001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.loader; 029 030import org.opencms.configuration.CmsParameterConfiguration; 031import org.opencms.file.CmsFile; 032import org.opencms.file.CmsObject; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsRequestContext; 035import org.opencms.file.CmsResource; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.CmsVfsResourceNotFoundException; 038import org.opencms.file.history.CmsHistoryResourceHandler; 039import org.opencms.flex.CmsFlexCache; 040import org.opencms.flex.CmsFlexController; 041import org.opencms.flex.CmsFlexRequest; 042import org.opencms.flex.CmsFlexResponse; 043import org.opencms.gwt.shared.CmsGwtConstants; 044import org.opencms.i18n.CmsEncoder; 045import org.opencms.i18n.CmsMessageContainer; 046import org.opencms.jsp.CmsJspTagEnableAde; 047import org.opencms.jsp.jsonpart.CmsJsonPartFilter; 048import org.opencms.jsp.util.CmsJspLinkMacroResolver; 049import org.opencms.jsp.util.CmsJspStandardContextBean; 050import org.opencms.main.CmsEvent; 051import org.opencms.main.CmsException; 052import org.opencms.main.CmsLog; 053import org.opencms.main.I_CmsEventListener; 054import org.opencms.main.OpenCms; 055import org.opencms.monitor.CmsMemoryMonitor; 056import org.opencms.relations.CmsRelation; 057import org.opencms.relations.CmsRelationFilter; 058import org.opencms.relations.CmsRelationType; 059import org.opencms.staticexport.CmsLinkManager; 060import org.opencms.util.CmsFileUtil; 061import org.opencms.util.CmsRequestUtil; 062import org.opencms.util.CmsStringUtil; 063import org.opencms.util.I_CmsRegexSubstitution; 064import org.opencms.workplace.CmsWorkplaceManager; 065 066import java.io.File; 067import java.io.FileNotFoundException; 068import java.io.FileOutputStream; 069import java.io.IOException; 070import java.io.UnsupportedEncodingException; 071import java.io.Writer; 072import java.net.SocketException; 073import java.util.Collection; 074import java.util.Collections; 075import java.util.HashMap; 076import java.util.HashSet; 077import java.util.Iterator; 078import java.util.LinkedHashSet; 079import java.util.Locale; 080import java.util.Map; 081import java.util.Set; 082import java.util.concurrent.locks.Lock; 083import java.util.concurrent.locks.ReentrantReadWriteLock; 084import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; 085import java.util.regex.Matcher; 086import java.util.regex.Pattern; 087 088import javax.servlet.ServletException; 089import javax.servlet.ServletRequest; 090import javax.servlet.ServletResponse; 091import javax.servlet.http.HttpServletRequest; 092import javax.servlet.http.HttpServletResponse; 093 094import org.apache.commons.logging.Log; 095 096import com.google.common.base.Splitter; 097 098/** 099 * The JSP loader which enables the execution of JSP in OpenCms.<p> 100 * 101 * Parameters supported by this loader:<dl> 102 * 103 * <dt>jsp.repository</dt><dd> 104 * (Optional) This is the root directory in the "real" file system where generated JSPs are stored. 105 * The default is the web application path, e.g. in Tomcat if your web application is 106 * names "opencms" it would be <code>${TOMCAT_HOME}/webapps/opencms/</code>. 107 * The <code>jsp.folder</code> (see below) is added to this path. 108 * Usually the <code>jsp.repository</code> is not changed. 109 * </dd> 110 * 111 * <dt>jsp.folder</dt><dd> 112 * (Optional) A path relative to the <code>jsp.repository</code> path where the 113 * JSPs generated by OpenCms are stored. The default is to store the generated JSP in 114 * <code>/WEB-INF/jsp/</code>. 115 * This works well in Tomcat 4, and the JSPs are 116 * not accessible directly from the outside this way, only through the OpenCms servlet. 117 * <i>Please note:</i> Some servlet environments (e.g. BEA Weblogic) do not permit 118 * JSPs to be stored under <code>/WEB-INF</code>. For environments like these, 119 * set the path to some place where JSPs can be accessed, e.g. <code>/jsp/</code> only. 120 * </dd> 121 * 122 * <dt>jsp.errorpage.committed</dt><dd> 123 * (Optional) This parameter controls behavior of JSP error pages 124 * i.e. <code><% page errorPage="..." %></code>. If you find that these don't work 125 * in your servlet environment, you should try to change the value here. 126 * The default <code>true</code> has been tested with Tomcat 4.1 and 5.0. 127 * Older versions of Tomcat like 4.0 require a setting of <code>false</code>.</dd> 128 * </dl> 129 * 130 * @since 6.0.0 131 * 132 * @see I_CmsResourceLoader 133 */ 134public class CmsJspLoader implements I_CmsResourceLoader, I_CmsFlexCacheEnabledLoader, I_CmsEventListener { 135 136 /** Property value for "cache" that indicates that the FlexCache should be bypassed. */ 137 public static final String CACHE_PROPERTY_BYPASS = "bypass"; 138 139 /** Property value for "cache" that indicates that the output should be streamed. */ 140 public static final String CACHE_PROPERTY_STREAM = "stream"; 141 142 /** Default jsp folder constant. */ 143 public static final String DEFAULT_JSP_FOLDER = "/WEB-INF/jsp/"; 144 145 /** Special JSP directive tag start (<code>%></code>). */ 146 public static final String DIRECTIVE_END = "%>"; 147 148 /** Special JSP directive tag start (<code><%(</code>). */ 149 public static final String DIRECTIVE_START = "<%@"; 150 151 /** Extension for JSP managed by OpenCms (<code>.jsp</code>). */ 152 public static final String JSP_EXTENSION = ".jsp"; 153 154 /** Cache max age parameter name. */ 155 public static final String PARAM_CLIENT_CACHE_MAXAGE = "client.cache.maxage"; 156 157 /** Jsp cache size parameter name. */ 158 public static final String PARAM_JSP_CACHE_SIZE = "jsp.cache.size"; 159 160 /** Error page committed parameter name. */ 161 public static final String PARAM_JSP_ERRORPAGE_COMMITTED = "jsp.errorpage.committed"; 162 163 /** Jsp folder parameter name. */ 164 public static final String PARAM_JSP_FOLDER = "jsp.folder"; 165 166 /** Jsp repository parameter name. */ 167 public static final String PARAM_JSP_REPOSITORY = "jsp.repository"; 168 169 /** The id of this loader. */ 170 public static final int RESOURCE_LOADER_ID = 6; 171 172 /** The log object for this class. */ 173 private static final Log LOG = CmsLog.getLog(CmsJspLoader.class); 174 175 /** The maximum age for delivered contents in the clients cache. */ 176 private static long m_clientCacheMaxAge; 177 178 /** Read write locks for jsp files. */ 179 private static Map<String, ReentrantReadWriteLock> m_fileLocks; 180 181 /** The directory to store the generated JSP pages in (absolute path). */ 182 private static String m_jspRepository; 183 184 /** The directory to store the generated JSP pages in (relative path in web application). */ 185 private static String m_jspWebAppRepository; 186 187 /** The CmsFlexCache used to store generated cache entries in. */ 188 private CmsFlexCache m_cache; 189 190 /** The resource loader configuration. */ 191 private CmsParameterConfiguration m_configuration; 192 193 /** Flag to indicate if error pages are marked as "committed". */ 194 private boolean m_errorPagesAreNotCommitted; 195 196 /** The offline JSPs. */ 197 private Map<String, Boolean> m_offlineJsps; 198 199 /** The online JSPs. */ 200 private Map<String, Boolean> m_onlineJsps; 201 202 /** A map from taglib names to their URIs. */ 203 private Map<String, String> m_taglibs = new HashMap<String, String>(); 204 205 /** Lock used to prevent JSP repository from being accessed while it is purged. The read lock is needed for accessing the JSP repository, the write lock is needed for purging it. */ 206 private ReentrantReadWriteLock m_purgeLock = new ReentrantReadWriteLock(true); 207 208 /** 209 * The constructor of the class is empty, the initial instance will be 210 * created by the resource manager upon startup of OpenCms.<p> 211 * 212 * @see org.opencms.loader.CmsResourceManager 213 */ 214 public CmsJspLoader() { 215 216 m_configuration = new CmsParameterConfiguration(); 217 OpenCms.addCmsEventListener( 218 this, 219 new int[] {EVENT_CLEAR_CACHES, EVENT_CLEAR_OFFLINE_CACHES, EVENT_CLEAR_ONLINE_CACHES}); 220 m_fileLocks = CmsMemoryMonitor.createLRUCacheMap(10000); 221 initCaches(1000); 222 } 223 224 /** 225 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) 226 */ 227 public void addConfigurationParameter(String paramName, String paramValue) { 228 229 m_configuration.add(paramName, paramValue); 230 if (paramName.startsWith("taglib.")) { 231 m_taglibs.put(paramName.replaceFirst("^taglib\\.", ""), paramValue.trim()); 232 } 233 } 234 235 /** 236 * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent) 237 */ 238 public void cmsEvent(CmsEvent event) { 239 240 switch (event.getType()) { 241 case EVENT_CLEAR_CACHES: 242 m_offlineJsps.clear(); 243 m_onlineJsps.clear(); 244 return; 245 case EVENT_CLEAR_OFFLINE_CACHES: 246 m_offlineJsps.clear(); 247 return; 248 case EVENT_CLEAR_ONLINE_CACHES: 249 m_onlineJsps.clear(); 250 return; 251 default: 252 // do nothing 253 } 254 } 255 256 /** 257 * Destroy this ResourceLoder, this is a NOOP so far. 258 */ 259 public void destroy() { 260 261 // NOOP 262 } 263 264 /** 265 * @see org.opencms.loader.I_CmsResourceLoader#dump(org.opencms.file.CmsObject, org.opencms.file.CmsResource, java.lang.String, java.util.Locale, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 266 */ 267 public byte[] dump( 268 CmsObject cms, 269 CmsResource file, 270 String element, 271 Locale locale, 272 HttpServletRequest req, 273 HttpServletResponse res) 274 throws ServletException, IOException { 275 276 // get the current Flex controller 277 CmsFlexController controller = CmsFlexController.getController(req); 278 CmsFlexController oldController = null; 279 280 if (controller != null) { 281 // for dumping we must create an new "top level" controller, save the old one to be restored later 282 oldController = controller; 283 } 284 285 byte[] result = null; 286 try { 287 // now create a new, temporary Flex controller 288 controller = getController(cms, file, req, res, false, false); 289 if (element != null) { 290 // add the element parameter to the included request 291 String[] value = new String[] {element}; 292 Map<String, String[]> parameters = Collections.singletonMap( 293 I_CmsResourceLoader.PARAMETER_ELEMENT, 294 value); 295 controller.getCurrentRequest().addParameterMap(parameters); 296 } 297 controller.getCurrentRequest().addAttributeMap(CmsRequestUtil.getAtrributeMap(req)); 298 // dispatch to the JSP 299 result = dispatchJsp(controller); 300 // remove temporary controller 301 CmsFlexController.removeController(req); 302 } finally { 303 if ((oldController != null) && (controller != null)) { 304 // update "date last modified" 305 oldController.updateDates(controller.getDateLastModified(), controller.getDateExpires()); 306 // reset saved controller 307 CmsFlexController.setController(req, oldController); 308 } 309 } 310 311 return result; 312 } 313 314 /** 315 * @see org.opencms.loader.I_CmsResourceLoader#export(org.opencms.file.CmsObject, org.opencms.file.CmsResource, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 316 */ 317 public byte[] export(CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res) 318 throws ServletException, IOException { 319 320 // get the Flex controller 321 CmsFlexController controller = getController(cms, resource, req, res, false, true); 322 323 // dispatch to the JSP 324 byte[] result = dispatchJsp(controller); 325 326 // remove the controller from the request 327 CmsFlexController.removeController(req); 328 329 // return the contents 330 return result; 331 } 332 333 /** 334 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() 335 */ 336 public CmsParameterConfiguration getConfiguration() { 337 338 // return the configuration in an immutable form 339 return m_configuration; 340 } 341 342 /** 343 * Returns the absolute path in the "real" file system for the JSP repository 344 * toplevel directory.<p> 345 * 346 * @return The full path to the JSP repository 347 */ 348 public String getJspRepository() { 349 350 return m_jspRepository; 351 } 352 353 /** 354 * @see org.opencms.loader.I_CmsResourceLoader#getLoaderId() 355 */ 356 public int getLoaderId() { 357 358 return RESOURCE_LOADER_ID; 359 } 360 361 /** 362 * Returns a set of root paths of files that are including the given resource using the 'link.strong' macro.<p> 363 * 364 * @param cms the current cms context 365 * @param resource the resource to check 366 * @param referencingPaths the set of already referencing paths, also return parameter 367 * 368 * @throws CmsException if something goes wrong 369 */ 370 public void getReferencingStrongLinks(CmsObject cms, CmsResource resource, Set<String> referencingPaths) 371 throws CmsException { 372 373 CmsRelationFilter filter = CmsRelationFilter.SOURCES.filterType(CmsRelationType.JSP_STRONG); 374 Iterator<CmsRelation> it = cms.getRelationsForResource(resource, filter).iterator(); 375 while (it.hasNext()) { 376 CmsRelation relation = it.next(); 377 try { 378 CmsResource source = relation.getSource(cms, CmsResourceFilter.DEFAULT); 379 // check if file was already included 380 if (referencingPaths.contains(source.getRootPath())) { 381 // no need to include this file more than once 382 continue; 383 } 384 referencingPaths.add(source.getRootPath()); 385 getReferencingStrongLinks(cms, source, referencingPaths); 386 } catch (CmsException e) { 387 if (LOG.isErrorEnabled()) { 388 LOG.error(e.getLocalizedMessage(), e); 389 } 390 } 391 } 392 } 393 394 /** 395 * Return a String describing the ResourceLoader, 396 * which is (localized to the system default locale) 397 * <code>"The OpenCms default resource loader for JSP"</code>.<p> 398 * 399 * @return a describing String for the ResourceLoader 400 */ 401 public String getResourceLoaderInfo() { 402 403 return Messages.get().getBundle().key(Messages.GUI_LOADER_JSP_DEFAULT_DESC_0); 404 } 405 406 /** 407 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() 408 */ 409 public void initConfiguration() { 410 411 m_jspRepository = m_configuration.get(PARAM_JSP_REPOSITORY); 412 if (m_jspRepository == null) { 413 m_jspRepository = OpenCms.getSystemInfo().getWebApplicationRfsPath(); 414 } 415 m_jspWebAppRepository = m_configuration.getString(PARAM_JSP_FOLDER, DEFAULT_JSP_FOLDER); 416 if (!m_jspWebAppRepository.endsWith("/")) { 417 m_jspWebAppRepository += "/"; 418 } 419 m_jspRepository = CmsFileUtil.normalizePath(m_jspRepository + m_jspWebAppRepository); 420 421 String maxAge = m_configuration.get(PARAM_CLIENT_CACHE_MAXAGE); 422 if (maxAge == null) { 423 m_clientCacheMaxAge = -1; 424 } else { 425 m_clientCacheMaxAge = Long.parseLong(maxAge); 426 } 427 428 // get the "error pages are committed or not" flag from the configuration 429 m_errorPagesAreNotCommitted = m_configuration.getBoolean(PARAM_JSP_ERRORPAGE_COMMITTED, true); 430 431 int cacheSize = m_configuration.getInteger(PARAM_JSP_CACHE_SIZE, -1); 432 if (cacheSize > 0) { 433 initCaches(cacheSize); 434 } 435 436 // output setup information 437 if (CmsLog.INIT.isInfoEnabled()) { 438 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_JSP_REPOSITORY_ABS_PATH_1, m_jspRepository)); 439 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WEBAPP_PATH_1, m_jspWebAppRepository)); 440 CmsLog.INIT.info( 441 Messages.get().getBundle().key( 442 Messages.INIT_JSP_REPOSITORY_ERR_PAGE_COMMOTED_1, 443 Boolean.valueOf(m_errorPagesAreNotCommitted))); 444 if (m_clientCacheMaxAge > 0) { 445 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_CLIENT_CACHE_MAX_AGE_1, maxAge)); 446 } 447 if (cacheSize > 0) { 448 CmsLog.INIT.info( 449 Messages.get().getBundle().key(Messages.INIT_JSP_CACHE_SIZE_1, String.valueOf(cacheSize))); 450 } 451 CmsLog.INIT.info( 452 Messages.get().getBundle().key(Messages.INIT_LOADER_INITIALIZED_1, this.getClass().getName())); 453 } 454 } 455 456 /** 457 * @see org.opencms.loader.I_CmsResourceLoader#isStaticExportEnabled() 458 */ 459 public boolean isStaticExportEnabled() { 460 461 return true; 462 } 463 464 /** 465 * @see org.opencms.loader.I_CmsResourceLoader#isStaticExportProcessable() 466 */ 467 public boolean isStaticExportProcessable() { 468 469 return true; 470 } 471 472 /** 473 * @see org.opencms.loader.I_CmsResourceLoader#isUsableForTemplates() 474 */ 475 public boolean isUsableForTemplates() { 476 477 return true; 478 } 479 480 /** 481 * @see org.opencms.loader.I_CmsResourceLoader#isUsingUriWhenLoadingTemplate() 482 */ 483 public boolean isUsingUriWhenLoadingTemplate() { 484 485 return false; 486 } 487 488 /** 489 * @see org.opencms.loader.I_CmsResourceLoader#load(org.opencms.file.CmsObject, org.opencms.file.CmsResource, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 490 */ 491 public void load(CmsObject cms, CmsResource file, HttpServletRequest req, HttpServletResponse res) 492 throws ServletException, IOException, CmsException { 493 494 CmsRequestContext context = cms.getRequestContext(); 495 // If we load template jsp or template-element jsp (xml contents or xml pages) don't show source (2nd test) 496 if ((CmsHistoryResourceHandler.isHistoryRequest(req)) 497 && (context.getUri().equals(context.removeSiteRoot(file.getRootPath())))) { 498 showSource(cms, file, req, res); 499 } else { 500 // load and process the JSP 501 boolean streaming = false; 502 boolean bypass = false; 503 504 // read "cache" property for requested VFS resource to check for special "stream" and "bypass" values 505 String cacheProperty = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CACHE, true).getValue(); 506 if (cacheProperty != null) { 507 cacheProperty = cacheProperty.trim(); 508 if (CACHE_PROPERTY_STREAM.equals(cacheProperty)) { 509 streaming = true; 510 } else if (CACHE_PROPERTY_BYPASS.equals(cacheProperty)) { 511 streaming = true; 512 bypass = true; 513 } 514 } 515 516 // For now, disable flex caching when the __json parameter is used 517 if (CmsJsonPartFilter.isJsonRequest(req)) { 518 streaming = true; 519 bypass = true; 520 } 521 522 // get the Flex controller 523 CmsFlexController controller = getController(cms, file, req, res, streaming, true); 524 Lock lock = m_purgeLock.readLock(); 525 try { 526 lock.lock(); 527 if (bypass || controller.isForwardMode()) { 528 // initialize the standard contex bean to be available for all requests 529 CmsJspStandardContextBean.getInstance(controller.getCurrentRequest()); 530 // once in forward mode, always in forward mode (for this request) 531 controller.setForwardMode(true); 532 // bypass Flex cache for this page, update the JSP first if necessary 533 String target = updateJsp(file, controller, new HashSet<String>()); 534 // dispatch to external JSP 535 req.getRequestDispatcher(target).forward(controller.getCurrentRequest(), res); 536 } else { 537 // Flex cache not bypassed, dispatch to internal JSP 538 dispatchJsp(controller); 539 } 540 } finally { 541 lock.unlock(); 542 } 543 544 // remove the controller from the request if not forwarding 545 if (!controller.isForwardMode()) { 546 CmsFlexController.removeController(req); 547 } 548 } 549 } 550 551 /** 552 * Replaces taglib attributes in page directives with taglib directives.<p> 553 * 554 * @param content the JSP source text 555 * 556 * @return the transformed JSP text 557 */ 558 public String processTaglibAttributes(String content) { 559 560 // matches a whole page directive 561 final Pattern directivePattern = Pattern.compile("(?sm)<%@\\s*page.*?%>"); 562 // matches a taglibs attribute and captures its values 563 final Pattern taglibPattern = Pattern.compile("(?sm)taglibs\\s*=\\s*\"(.*?)\""); 564 final Pattern commaPattern = Pattern.compile("(?sm)\\s*,\\s*"); 565 final Set<String> taglibs = new LinkedHashSet<String>(); 566 // we insert the marker after the first page directive 567 final String marker = ":::TAGLIBS:::"; 568 I_CmsRegexSubstitution directiveSub = new I_CmsRegexSubstitution() { 569 570 private boolean m_first = true; 571 572 public String substituteMatch(String string, Matcher matcher) { 573 574 String match = string.substring(matcher.start(), matcher.end()); 575 I_CmsRegexSubstitution taglibSub = new I_CmsRegexSubstitution() { 576 577 public String substituteMatch(String string1, Matcher matcher1) { 578 579 // values of the taglibs attribute 580 String match1 = string1.substring(matcher1.start(1), matcher1.end(1)); 581 for (String taglibKey : Splitter.on(commaPattern).split(match1)) { 582 taglibs.add(taglibKey); 583 } 584 return ""; 585 } 586 }; 587 String result = CmsStringUtil.substitute(taglibPattern, match, taglibSub); 588 if (m_first) { 589 result += marker; 590 m_first = false; 591 } 592 return result; 593 } 594 }; 595 String substituted = CmsStringUtil.substitute(directivePattern, content, directiveSub); 596 // insert taglib inclusion 597 substituted = substituted.replaceAll(marker, generateTaglibInclusions(taglibs)); 598 // remove empty page directives 599 substituted = substituted.replaceAll("(?sm)<%@\\s*page\\s*%>", ""); 600 return substituted; 601 } 602 603 /** 604 * Removes the given resources from the cache.<p> 605 * 606 * @param rootPaths the set of root paths to remove 607 * @param online if online or offline 608 */ 609 public void removeFromCache(Set<String> rootPaths, boolean online) { 610 611 Map<String, Boolean> cache; 612 if (online) { 613 cache = m_onlineJsps; 614 } else { 615 cache = m_offlineJsps; 616 } 617 Iterator<String> itRemove = rootPaths.iterator(); 618 while (itRemove.hasNext()) { 619 String rootPath = itRemove.next(); 620 cache.remove(rootPath); 621 } 622 } 623 624 /** 625 * Removes a JSP from an offline project from the RFS.<p> 626 * 627 * @param resource the offline JSP resource to remove from the RFS 628 * 629 * @throws CmsLoaderException if accessing the loader fails 630 */ 631 public void removeOfflineJspFromRepository(CmsResource resource) throws CmsLoaderException { 632 633 String jspName = getJspRfsPath(resource, false); 634 Set<String> pathSet = new HashSet<String>(); 635 pathSet.add(resource.getRootPath()); 636 ReentrantReadWriteLock lock = getFileLock(jspName); 637 lock.writeLock().lock(); 638 try { 639 removeFromCache(pathSet, false); 640 File jspFile = new File(jspName); 641 jspFile.delete(); 642 } finally { 643 lock.writeLock().unlock(); 644 } 645 } 646 647 /** 648 * @see org.opencms.loader.I_CmsResourceLoader#service(org.opencms.file.CmsObject, org.opencms.file.CmsResource, javax.servlet.ServletRequest, javax.servlet.ServletResponse) 649 */ 650 public void service(CmsObject cms, CmsResource resource, ServletRequest req, ServletResponse res) 651 throws ServletException, IOException, CmsLoaderException { 652 653 Lock lock = m_purgeLock.readLock(); 654 try { 655 lock.lock(); 656 657 CmsFlexController controller = CmsFlexController.getController(req); 658 // get JSP target name on "real" file system 659 String target = updateJsp(resource, controller, new HashSet<String>(8)); 660 // important: Indicate that all output must be buffered 661 controller.getCurrentResponse().setOnlyBuffering(true); 662 // initialize the standard contex bean to be available for all requests 663 CmsJspStandardContextBean.getInstance(controller.getCurrentRequest()); 664 // dispatch to external file 665 controller.getCurrentRequest().getRequestDispatcherToExternal(cms.getSitePath(resource), target).include( 666 req, 667 res); 668 } finally { 669 lock.unlock(); 670 } 671 } 672 673 /** 674 * @see org.opencms.loader.I_CmsFlexCacheEnabledLoader#setFlexCache(org.opencms.flex.CmsFlexCache) 675 */ 676 public void setFlexCache(CmsFlexCache cache) { 677 678 m_cache = cache; 679 // output setup information 680 if (CmsLog.INIT.isInfoEnabled()) { 681 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ADD_FLEX_CACHE_0)); 682 } 683 } 684 685 /** 686 * Triggers an asynchronous purge of the JSP repository.<p> 687 * 688 * @param afterPurgeAction the action to execute after purging 689 */ 690 public void triggerPurge(final Runnable afterPurgeAction) { 691 692 OpenCms.getExecutor().execute(new Runnable() { 693 694 @SuppressWarnings("synthetic-access") 695 public void run() { 696 697 WriteLock lock = m_purgeLock.writeLock(); 698 try { 699 lock.lock(); 700 doPurge(afterPurgeAction); 701 } catch (Exception e) { 702 LOG.error("Error while purging jsp repository: " + e.getLocalizedMessage(), e); 703 } finally { 704 lock.unlock(); 705 } 706 } 707 }); 708 } 709 710 /** 711 * Updates a JSP page in the "real" file system in case the VFS resource has changed.<p> 712 * 713 * Also processes the <code><%@ cms %></code> tags before the JSP is written to the real FS. 714 * Also recursively updates all files that are referenced by a <code><%@ cms %></code> tag 715 * on this page to make sure the file actually exists in the real FS. 716 * All <code><%@ include %></code> tags are parsed and the name in the tag is translated 717 * from the OpenCms VFS path to the path in the real FS. 718 * The same is done for filenames in <code><%@ page errorPage=... %></code> tags.<p> 719 * 720 * @param resource the requested JSP file resource in the VFS 721 * @param controller the controller for the JSP integration 722 * @param updatedFiles a Set containing all JSP pages that have been already updated 723 * 724 * @return the file name of the updated JSP in the "real" FS 725 * 726 * @throws ServletException might be thrown in the process of including the JSP 727 * @throws IOException might be thrown in the process of including the JSP 728 * @throws CmsLoaderException if the resource type can not be read 729 */ 730 public String updateJsp(CmsResource resource, CmsFlexController controller, Set<String> updatedFiles) 731 throws IOException, ServletException, CmsLoaderException { 732 733 String jspVfsName = resource.getRootPath(); 734 String extension; 735 boolean isHardInclude; 736 int loaderId = OpenCms.getResourceManager().getResourceType(resource.getTypeId()).getLoaderId(); 737 if ((loaderId == CmsJspLoader.RESOURCE_LOADER_ID) && (!jspVfsName.endsWith(JSP_EXTENSION))) { 738 // this is a true JSP resource that does not end with ".jsp" 739 extension = JSP_EXTENSION; 740 isHardInclude = false; 741 } else { 742 // not a JSP resource or already ends with ".jsp" 743 extension = ""; 744 // if this is a JSP we don't treat it as hard include 745 isHardInclude = (loaderId != CmsJspLoader.RESOURCE_LOADER_ID); 746 } 747 748 String jspTargetName = CmsFileUtil.getRepositoryName( 749 m_jspWebAppRepository, 750 jspVfsName + extension, 751 controller.getCurrentRequest().isOnline()); 752 753 // check if page was already updated 754 if (updatedFiles.contains(jspTargetName)) { 755 // no need to write the already included file to the real FS more then once 756 return jspTargetName; 757 } 758 759 String jspPath = CmsFileUtil.getRepositoryName( 760 m_jspRepository, 761 jspVfsName + extension, 762 controller.getCurrentRequest().isOnline()); 763 764 File d = new File(jspPath).getParentFile(); 765 if ((d == null) || (d.exists() && !(d.isDirectory() && d.canRead()))) { 766 CmsMessageContainer message = Messages.get().container(Messages.LOG_ACCESS_DENIED_1, jspPath); 767 LOG.error(message.key()); 768 // can not continue 769 throw new ServletException(message.key()); 770 } 771 772 if (!d.exists()) { 773 // create directory structure 774 d.mkdirs(); 775 } 776 ReentrantReadWriteLock readWriteLock = getFileLock(jspVfsName); 777 try { 778 // get a read lock for this jsp 779 readWriteLock.readLock().lock(); 780 File jspFile = new File(jspPath); 781 // check if the JSP must be updated 782 boolean mustUpdate = false; 783 long jspModificationDate = 0; 784 if (!jspFile.exists()) { 785 // file does not exist in real FS 786 mustUpdate = true; 787 // make sure the parent folder exists 788 File folder = jspFile.getParentFile(); 789 if (!folder.exists()) { 790 boolean success = folder.mkdirs(); 791 if (!success) { 792 LOG.error( 793 org.opencms.db.Messages.get().getBundle().key( 794 org.opencms.db.Messages.LOG_CREATE_FOLDER_FAILED_1, 795 folder.getAbsolutePath())); 796 } 797 } 798 } else { 799 jspModificationDate = jspFile.lastModified(); 800 if (jspModificationDate < resource.getDateLastModified()) { 801 // file in real FS is older then file in VFS 802 mustUpdate = true; 803 } else if (controller.getCurrentRequest().isDoRecompile()) { 804 // recompile is forced with parameter 805 mustUpdate = true; 806 } else { 807 // check if update is needed 808 if (controller.getCurrentRequest().isOnline()) { 809 mustUpdate = !m_onlineJsps.containsKey(jspVfsName); 810 } else { 811 mustUpdate = !m_offlineJsps.containsKey(jspVfsName); 812 } 813 // check strong links only if update is needed 814 if (mustUpdate) { 815 // update strong link dependencies 816 mustUpdate = updateStrongLinks(resource, controller, updatedFiles); 817 } 818 } 819 } 820 if (mustUpdate) { 821 if (LOG.isDebugEnabled()) { 822 LOG.debug(Messages.get().getBundle().key(Messages.LOG_WRITING_JSP_1, jspTargetName)); 823 } 824 // jsp needs updating, acquire a write lock 825 readWriteLock.readLock().unlock(); 826 readWriteLock.writeLock().lock(); 827 try { 828 // check again if updating is still necessary as this might have happened while waiting for the write lock 829 if (!jspFile.exists() || (jspModificationDate == jspFile.lastModified())) { 830 updatedFiles.add(jspTargetName); 831 byte[] contents; 832 String encoding; 833 try { 834 CmsObject cms = controller.getCmsObject(); 835 contents = cms.readFile(resource).getContents(); 836 // check the "content-encoding" property for the JSP, use system default if not found on path 837 encoding = cms.readPropertyObject( 838 resource, 839 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 840 true).getValue(); 841 if (encoding == null) { 842 encoding = OpenCms.getSystemInfo().getDefaultEncoding(); 843 } else { 844 encoding = CmsEncoder.lookupEncoding(encoding.trim(), encoding); 845 } 846 } catch (CmsException e) { 847 controller.setThrowable(e, jspVfsName); 848 throw new ServletException( 849 Messages.get().getBundle().key(Messages.ERR_LOADER_JSP_ACCESS_1, jspVfsName), 850 e); 851 } 852 853 try { 854 // parse the JSP and modify OpenCms critical directives 855 contents = parseJsp(contents, encoding, controller, updatedFiles, isHardInclude); 856 if (LOG.isInfoEnabled()) { 857 // check for existing file and display some debug info 858 LOG.info( 859 Messages.get().getBundle().key( 860 Messages.LOG_JSP_PERMCHECK_4, 861 new Object[] { 862 jspFile.getAbsolutePath(), 863 Boolean.valueOf(jspFile.exists()), 864 Boolean.valueOf(jspFile.isFile()), 865 Boolean.valueOf(jspFile.canWrite())})); 866 } 867 // write the parsed JSP content to the real FS 868 synchronized (CmsJspLoader.class) { 869 // this must be done only one file at a time 870 FileOutputStream fs = new FileOutputStream(jspFile); 871 fs.write(contents); 872 fs.close(); 873 874 // we set the modification date to (approximately) that of the VFS resource. This is needed because in the Online project, the old version of a JSP 875 // may be generated in the RFS JSP repository *after* the JSP has been changed, but *before* it has been published, which would lead 876 // to it not being updated after the changed JSP is published. 877 878 // Note: the RFS may only support second precision for the last modification date 879 jspFile.setLastModified((1 + (resource.getDateLastModified() / 1000)) * 1000); 880 } 881 if (controller.getCurrentRequest().isOnline()) { 882 m_onlineJsps.put(jspVfsName, Boolean.TRUE); 883 } else { 884 m_offlineJsps.put(jspVfsName, Boolean.TRUE); 885 } 886 if (LOG.isInfoEnabled()) { 887 LOG.info( 888 Messages.get().getBundle().key( 889 Messages.LOG_UPDATED_JSP_2, 890 jspTargetName, 891 jspVfsName)); 892 } 893 } catch (FileNotFoundException e) { 894 throw new ServletException( 895 Messages.get().getBundle().key(Messages.ERR_LOADER_JSP_WRITE_1, jspFile.getName()), 896 e); 897 } 898 } 899 } finally { 900 readWriteLock.readLock().lock(); 901 readWriteLock.writeLock().unlock(); 902 } 903 } 904 905 // update "last modified" and "expires" date on controller 906 controller.updateDates(jspFile.lastModified(), CmsResource.DATE_EXPIRED_DEFAULT); 907 } finally { 908 //m_processingFiles.remove(jspVfsName); 909 readWriteLock.readLock().unlock(); 910 } 911 912 return jspTargetName; 913 } 914 915 /** 916 * Updates the internal jsp repository when the servlet container 917 * tries to compile a jsp file that may not exist.<p> 918 * 919 * @param servletPath the servlet path, just to avoid unneeded recursive calls 920 * @param request the current request 921 */ 922 public void updateJspFromRequest(String servletPath, CmsFlexRequest request) { 923 924 // assemble the RFS name of the requested jsp 925 String jspUri = servletPath; 926 String pathInfo = request.getPathInfo(); 927 if (pathInfo != null) { 928 jspUri += pathInfo; 929 } 930 931 // check the file name 932 if ((jspUri == null) || !jspUri.startsWith(m_jspWebAppRepository)) { 933 // nothing to do, this kind of request are handled by the CmsJspLoader#service method 934 return; 935 } 936 937 // remove prefixes 938 jspUri = jspUri.substring(m_jspWebAppRepository.length()); 939 if (jspUri.startsWith(CmsFlexCache.REPOSITORY_ONLINE)) { 940 jspUri = jspUri.substring(CmsFlexCache.REPOSITORY_ONLINE.length()); 941 } else if (jspUri.startsWith(CmsFlexCache.REPOSITORY_OFFLINE)) { 942 jspUri = jspUri.substring(CmsFlexCache.REPOSITORY_OFFLINE.length()); 943 } else { 944 // this is not an OpenCms jsp file 945 return; 946 } 947 948 // read the resource from OpenCms 949 CmsFlexController controller = CmsFlexController.getController(request); 950 try { 951 CmsResource includeResource; 952 try { 953 // first try to read the resource assuming no additional jsp extension was needed 954 includeResource = readJspResource(controller, jspUri); 955 } catch (CmsVfsResourceNotFoundException e) { 956 // try removing the additional jsp extension 957 if (jspUri.endsWith(JSP_EXTENSION)) { 958 jspUri = jspUri.substring(0, jspUri.length() - JSP_EXTENSION.length()); 959 } 960 includeResource = readJspResource(controller, jspUri); 961 } 962 // make sure the jsp referenced file is generated 963 updateJsp(includeResource, controller, new HashSet<String>(8)); 964 } catch (Exception e) { 965 if (LOG.isDebugEnabled()) { 966 LOG.debug(e.getLocalizedMessage(), e); 967 } 968 } 969 } 970 971 /** 972 * Dispatches the current request to the OpenCms internal JSP.<p> 973 * 974 * @param controller the current controller 975 * 976 * @return the content of the processed JSP 977 * 978 * @throws ServletException if inclusion does not work 979 * @throws IOException if inclusion does not work 980 */ 981 protected byte[] dispatchJsp(CmsFlexController controller) throws ServletException, IOException { 982 983 // get request / response wrappers 984 CmsFlexRequest f_req = controller.getCurrentRequest(); 985 CmsFlexResponse f_res = controller.getCurrentResponse(); 986 try { 987 f_req.getRequestDispatcher(controller.getCmsObject().getSitePath(controller.getCmsResource())).include( 988 f_req, 989 f_res); 990 } catch (SocketException e) { 991 // uncritical, might happen if client (browser) does not wait until end of page delivery 992 LOG.debug(Messages.get().getBundle().key(Messages.LOG_IGNORING_EXC_1, e.getClass().getName()), e); 993 } 994 995 byte[] result = null; 996 HttpServletResponse res = controller.getTopResponse(); 997 998 if (!controller.isStreaming() && !f_res.isSuspended()) { 999 try { 1000 // if a JSP error page was triggered the response will be already committed here 1001 if (!res.isCommitted() || m_errorPagesAreNotCommitted) { 1002 1003 // check if the current request was done by a workplace user 1004 boolean isWorkplaceUser = CmsWorkplaceManager.isWorkplaceUser(f_req); 1005 1006 // check if the content was modified since the last request 1007 if (controller.isTop() 1008 && !isWorkplaceUser 1009 && CmsFlexController.isNotModifiedSince(f_req, controller.getDateLastModified())) { 1010 if (f_req.getParameterMap().size() == 0) { 1011 // only use "expires" header on pages that have no parameters, 1012 // otherwise some browsers (e.g. IE 6) will not even try to request 1013 // updated versions of the page 1014 CmsFlexController.setDateExpiresHeader( 1015 res, 1016 controller.getDateExpires(), 1017 m_clientCacheMaxAge); 1018 } 1019 res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 1020 return null; 1021 } 1022 1023 // get the result byte array 1024 result = f_res.getWriterBytes(); 1025 HttpServletRequest req = controller.getTopRequest(); 1026 if (req.getHeader(CmsRequestUtil.HEADER_OPENCMS_EXPORT) != null) { 1027 // this is a non "on-demand" static export request, don't write to the response stream 1028 req.setAttribute( 1029 CmsRequestUtil.HEADER_OPENCMS_EXPORT, 1030 new Long(controller.getDateLastModified())); 1031 } else if (controller.isTop()) { 1032 // process headers and write output if this is the "top" request/response 1033 res.setContentLength(result.length); 1034 // check for preset error code 1035 Integer errorCode = (Integer)req.getAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE); 1036 if (errorCode == null) { 1037 // set last modified / no cache headers only if this is not an error page 1038 if (isWorkplaceUser) { 1039 res.setDateHeader(CmsRequestUtil.HEADER_LAST_MODIFIED, System.currentTimeMillis()); 1040 CmsRequestUtil.setNoCacheHeaders(res); 1041 } else { 1042 // set date last modified header 1043 CmsFlexController.setDateLastModifiedHeader(res, controller.getDateLastModified()); 1044 if ((f_req.getParameterMap().size() == 0) && (controller.getDateLastModified() > -1)) { 1045 // only use "expires" header on pages that have no parameters 1046 // and that are cachable (i.e. 'date last modified' is set) 1047 // otherwise some browsers (e.g. IE 6) will not even try to request 1048 // updated versions of the page 1049 CmsFlexController.setDateExpiresHeader( 1050 res, 1051 controller.getDateExpires(), 1052 m_clientCacheMaxAge); 1053 } 1054 } 1055 // set response status to "200 - OK" (required for static export "on-demand") 1056 res.setStatus(HttpServletResponse.SC_OK); 1057 } else { 1058 // set previously saved error code 1059 res.setStatus(errorCode.intValue()); 1060 } 1061 // process the headers 1062 CmsFlexResponse.processHeaders(f_res.getHeaders(), res); 1063 res.getOutputStream().write(result); 1064 res.getOutputStream().flush(); 1065 } 1066 } 1067 } catch (IllegalStateException e) { 1068 // uncritical, might happen if JSP error page was used 1069 LOG.debug(Messages.get().getBundle().key(Messages.LOG_IGNORING_EXC_1, e.getClass().getName()), e); 1070 } catch (SocketException e) { 1071 // uncritical, might happen if client (browser) does not wait until end of page delivery 1072 LOG.debug(Messages.get().getBundle().key(Messages.LOG_IGNORING_EXC_1, e.getClass().getName()), e); 1073 } 1074 } 1075 1076 return result; 1077 } 1078 1079 /** 1080 * Purges the JSP repository.<p< 1081 * 1082 * @param afterPurgeAction the action to execute after purging 1083 */ 1084 protected void doPurge(Runnable afterPurgeAction) { 1085 1086 if (LOG.isInfoEnabled()) { 1087 LOG.info( 1088 org.opencms.flex.Messages.get().getBundle().key( 1089 org.opencms.flex.Messages.LOG_FLEXCACHE_WILL_PURGE_JSP_REPOSITORY_0)); 1090 } 1091 1092 File d; 1093 d = new File(getJspRepository() + CmsFlexCache.REPOSITORY_ONLINE + File.separator); 1094 CmsFileUtil.purgeDirectory(d); 1095 1096 d = new File(getJspRepository() + CmsFlexCache.REPOSITORY_OFFLINE + File.separator); 1097 CmsFileUtil.purgeDirectory(d); 1098 if (afterPurgeAction != null) { 1099 afterPurgeAction.run(); 1100 } 1101 1102 if (LOG.isInfoEnabled()) { 1103 LOG.info( 1104 org.opencms.flex.Messages.get().getBundle().key( 1105 org.opencms.flex.Messages.LOG_FLEXCACHE_PURGED_JSP_REPOSITORY_0)); 1106 } 1107 1108 } 1109 1110 /** 1111 * Generates the taglib directives for a collection of taglib identifiers.<p> 1112 * 1113 * @param taglibs the taglib identifiers 1114 * 1115 * @return a string containing taglib directives 1116 */ 1117 protected String generateTaglibInclusions(Collection<String> taglibs) { 1118 1119 StringBuffer buffer = new StringBuffer(); 1120 for (String taglib : taglibs) { 1121 String uri = m_taglibs.get(taglib); 1122 if (uri != null) { 1123 buffer.append("<%@ taglib prefix=\"" + taglib + "\" uri=\"" + uri + "\" %>"); 1124 } 1125 } 1126 return buffer.toString(); 1127 } 1128 1129 /** 1130 * Delivers a Flex controller, either by creating a new one, or by re-using an existing one.<p> 1131 * 1132 * @param cms the initial CmsObject to wrap in the controller 1133 * @param resource the resource requested 1134 * @param req the current request 1135 * @param res the current response 1136 * @param streaming indicates if the response is streaming 1137 * @param top indicates if the response is the top response 1138 * 1139 * @return a Flex controller 1140 */ 1141 protected CmsFlexController getController( 1142 CmsObject cms, 1143 CmsResource resource, 1144 HttpServletRequest req, 1145 HttpServletResponse res, 1146 boolean streaming, 1147 boolean top) { 1148 1149 CmsFlexController controller = null; 1150 if (top) { 1151 // only check for existing controller if this is the "top" request/response 1152 controller = CmsFlexController.getController(req); 1153 } 1154 if (controller == null) { 1155 // create new request / response wrappers 1156 if (!cms.getRequestContext().getCurrentProject().isOnlineProject() 1157 && (CmsHistoryResourceHandler.isHistoryRequest(req) || CmsJspTagEnableAde.isDirectEditDisabled(req))) { 1158 cms.getRequestContext().setAttribute(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT, Boolean.TRUE); 1159 } 1160 controller = new CmsFlexController(cms, resource, m_cache, req, res, streaming, top); 1161 CmsFlexController.setController(req, controller); 1162 CmsFlexRequest f_req = new CmsFlexRequest(req, controller); 1163 CmsFlexResponse f_res = new CmsFlexResponse(res, controller, streaming, true); 1164 controller.push(f_req, f_res); 1165 } else if (controller.isForwardMode()) { 1166 // reset CmsObject (because of URI) if in forward mode 1167 controller = new CmsFlexController(cms, controller); 1168 CmsFlexController.setController(req, controller); 1169 } 1170 return controller; 1171 } 1172 1173 /** 1174 * Initializes the caches.<p> 1175 * 1176 * @param cacheSize the cache size 1177 */ 1178 protected void initCaches(int cacheSize) { 1179 1180 m_offlineJsps = CmsMemoryMonitor.createLRUCacheMap(cacheSize); 1181 m_onlineJsps = CmsMemoryMonitor.createLRUCacheMap(cacheSize); 1182 } 1183 1184 /** 1185 * Parses the JSP and modifies OpenCms critical directive information.<p> 1186 * 1187 * @param byteContent the original JSP content 1188 * @param encoding the encoding to use for the JSP 1189 * @param controller the controller for the JSP integration 1190 * @param updatedFiles a Set containing all JSP pages that have been already updated 1191 * @param isHardInclude indicated if this page is actually a "hard" include with <code><%@ include file="..." ></code> 1192 * 1193 * @return the modified JSP content 1194 */ 1195 protected byte[] parseJsp( 1196 byte[] byteContent, 1197 String encoding, 1198 CmsFlexController controller, 1199 Set<String> updatedFiles, 1200 boolean isHardInclude) { 1201 1202 String content; 1203 // make sure encoding is set correctly 1204 try { 1205 content = new String(byteContent, encoding); 1206 } catch (UnsupportedEncodingException e) { 1207 // encoding property is not set correctly 1208 LOG.error( 1209 Messages.get().getBundle().key( 1210 Messages.LOG_UNSUPPORTED_ENC_1, 1211 controller.getCurrentRequest().getElementUri()), 1212 e); 1213 try { 1214 encoding = OpenCms.getSystemInfo().getDefaultEncoding(); 1215 content = new String(byteContent, encoding); 1216 } catch (UnsupportedEncodingException e2) { 1217 // should not happen since default encoding is always a valid encoding (checked during system startup) 1218 content = new String(byteContent); 1219 } 1220 } 1221 1222 // parse for special %(link:...) macros 1223 content = parseJspLinkMacros(content, controller); 1224 // parse for special <%@cms file="..." %> tag 1225 content = parseJspCmsTag(content, controller, updatedFiles); 1226 // parse for included files in tags 1227 content = parseJspIncludes(content, controller, updatedFiles); 1228 // parse for <%@page pageEncoding="..." %> tag 1229 content = parseJspEncoding(content, encoding, isHardInclude); 1230 // Processes magic taglib attributes in page directives 1231 content = processTaglibAttributes(content); 1232 // convert the result to bytes and return it 1233 try { 1234 return content.getBytes(encoding); 1235 } catch (UnsupportedEncodingException e) { 1236 // should not happen since encoding was already checked 1237 return content.getBytes(); 1238 } 1239 } 1240 1241 /** 1242 * Parses the JSP content for the special <code><%cms file="..." %></code> tag.<p> 1243 * 1244 * @param content the JSP content to parse 1245 * @param controller the current JSP controller 1246 * @param updatedFiles a set of already updated jsp files 1247 * 1248 * @return the parsed JSP content 1249 */ 1250 protected String parseJspCmsTag(String content, CmsFlexController controller, Set<String> updatedFiles) { 1251 1252 // check if a JSP directive occurs in the file 1253 int i1 = content.indexOf(DIRECTIVE_START); 1254 if (i1 < 0) { 1255 // no directive occurs 1256 return content; 1257 } 1258 1259 StringBuffer buf = new StringBuffer(content.length()); 1260 int p0 = 0, i2 = 0, slen = DIRECTIVE_START.length(), elen = DIRECTIVE_END.length(); 1261 1262 while (i1 >= 0) { 1263 // parse the file and replace JSP filename references 1264 i2 = content.indexOf(DIRECTIVE_END, i1 + slen); 1265 if (i2 < 0) { 1266 // wrong syntax (missing end directive) - let the JSP compiler produce the error message 1267 return content; 1268 } else if (i2 > i1) { 1269 String directive = content.substring(i1 + slen, i2); 1270 if (LOG.isDebugEnabled()) { 1271 LOG.debug( 1272 Messages.get().getBundle().key( 1273 Messages.LOG_DIRECTIVE_DETECTED_3, 1274 DIRECTIVE_START, 1275 directive, 1276 DIRECTIVE_END)); 1277 } 1278 1279 int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; 1280 while (directive.charAt(t1) == ' ') { 1281 t1++; 1282 } 1283 String argument = null; 1284 if (directive.startsWith("cms", t1)) { 1285 if (LOG.isDebugEnabled()) { 1286 LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "cms")); 1287 } 1288 t2 = directive.indexOf("file", t1 + 3); 1289 t5 = 4; 1290 } 1291 1292 if (t2 > 0) { 1293 String sub = directive.substring(t2 + t5); 1294 char c1 = sub.charAt(t3); 1295 while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) { 1296 c1 = sub.charAt(++t3); 1297 } 1298 t4 = t3; 1299 while (c1 != '"') { 1300 c1 = sub.charAt(++t4); 1301 } 1302 if (t4 > t3) { 1303 argument = sub.substring(t3, t4); 1304 } 1305 if (LOG.isDebugEnabled()) { 1306 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DIRECTIVE_ARG_1, argument)); 1307 } 1308 } 1309 1310 if (argument != null) { 1311 // try to update the referenced file 1312 String jspname = updateJsp(argument, controller, updatedFiles); 1313 if (jspname != null) { 1314 directive = jspname; 1315 if (LOG.isDebugEnabled()) { 1316 LOG.debug( 1317 Messages.get().getBundle().key( 1318 Messages.LOG_DIRECTIVE_CHANGED_3, 1319 DIRECTIVE_START, 1320 directive, 1321 DIRECTIVE_END)); 1322 } 1323 } 1324 // cms directive was found 1325 buf.append(content.substring(p0, i1)); 1326 buf.append(directive); 1327 p0 = i2 + elen; 1328 i1 = content.indexOf(DIRECTIVE_START, p0); 1329 } else { 1330 // cms directive was not found 1331 buf.append(content.substring(p0, i1 + slen)); 1332 buf.append(directive); 1333 p0 = i2; 1334 i1 = content.indexOf(DIRECTIVE_START, p0); 1335 } 1336 } 1337 } 1338 if (i2 > 0) { 1339 // the content of the JSP was changed 1340 buf.append(content.substring(p0, content.length())); 1341 content = buf.toString(); 1342 } 1343 return content; 1344 } 1345 1346 /** 1347 * Parses the JSP content for the <code><%page pageEncoding="..." %></code> tag 1348 * and ensures that the JSP page encoding is set according to the OpenCms 1349 * "content-encoding" property value of the JSP.<p> 1350 * 1351 * @param content the JSP content to parse 1352 * @param encoding the encoding to use for the JSP 1353 * @param isHardInclude indicated if this page is actually a "hard" include with <code><%@ include file="..." ></code> 1354 * 1355 * @return the parsed JSP content 1356 */ 1357 protected String parseJspEncoding(String content, String encoding, boolean isHardInclude) { 1358 1359 // check if a JSP directive occurs in the file 1360 int i1 = content.indexOf(DIRECTIVE_START); 1361 if (i1 < 0) { 1362 // no directive occurs 1363 if (isHardInclude) { 1364 return content; 1365 } 1366 } 1367 1368 StringBuffer buf = new StringBuffer(content.length() + 64); 1369 int p0 = 0, i2 = 0, slen = DIRECTIVE_START.length(); 1370 boolean found = false; 1371 1372 if (i1 < 0) { 1373 // no directive found at all, append content to buffer 1374 buf.append(content); 1375 } 1376 1377 while (i1 >= 0) { 1378 // parse the file and set/replace page encoding 1379 i2 = content.indexOf(DIRECTIVE_END, i1 + slen); 1380 if (i2 < 0) { 1381 // wrong syntax (missing end directive) - let the JSP compiler produce the error message 1382 return content; 1383 } else if (i2 > i1) { 1384 String directive = content.substring(i1 + slen, i2); 1385 if (LOG.isDebugEnabled()) { 1386 LOG.debug( 1387 Messages.get().getBundle().key( 1388 Messages.LOG_DIRECTIVE_DETECTED_3, 1389 DIRECTIVE_START, 1390 directive, 1391 DIRECTIVE_END)); 1392 } 1393 1394 int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; 1395 while (directive.charAt(t1) == ' ') { 1396 t1++; 1397 } 1398 String argument = null; 1399 if (directive.startsWith("page", t1)) { 1400 if (LOG.isDebugEnabled()) { 1401 LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "page")); 1402 } 1403 t2 = directive.indexOf("pageEncoding", t1 + 4); 1404 t5 = 12; 1405 if (t2 > 0) { 1406 found = true; 1407 } 1408 } 1409 1410 if (t2 > 0) { 1411 String sub = directive.substring(t2 + t5); 1412 char c1 = sub.charAt(t3); 1413 while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) { 1414 c1 = sub.charAt(++t3); 1415 } 1416 t4 = t3; 1417 while (c1 != '"') { 1418 c1 = sub.charAt(++t4); 1419 } 1420 if (t4 > t3) { 1421 argument = sub.substring(t3, t4); 1422 } 1423 if (LOG.isDebugEnabled()) { 1424 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DIRECTIVE_ARG_1, argument)); 1425 } 1426 } 1427 1428 if (argument != null) { 1429 // a pageEncoding setting was found, changes have to be made 1430 String pre = directive.substring(0, t2 + t3 + t5); 1431 String suf = directive.substring(t2 + t3 + t5 + argument.length()); 1432 // change the encoding 1433 directive = pre + encoding + suf; 1434 if (LOG.isDebugEnabled()) { 1435 LOG.debug( 1436 Messages.get().getBundle().key( 1437 Messages.LOG_DIRECTIVE_CHANGED_3, 1438 DIRECTIVE_START, 1439 directive, 1440 DIRECTIVE_END)); 1441 } 1442 } 1443 1444 buf.append(content.substring(p0, i1 + slen)); 1445 buf.append(directive); 1446 p0 = i2; 1447 i1 = content.indexOf(DIRECTIVE_START, p0); 1448 } 1449 } 1450 if (i2 > 0) { 1451 // the content of the JSP was changed 1452 buf.append(content.substring(p0, content.length())); 1453 } 1454 if (found) { 1455 content = buf.toString(); 1456 } else if (!isHardInclude) { 1457 // encoding setting was not found 1458 // if this is not a "hard" include then add the encoding to the top of the page 1459 // checking for the hard include is important to prevent errors with 1460 // multiple page encoding settings if a template is composed from several hard included elements 1461 // this is an issue in Tomcat 4.x but not 5.x 1462 StringBuffer buf2 = new StringBuffer(buf.length() + 32); 1463 buf2.append("<%@ page pageEncoding=\""); 1464 buf2.append(encoding); 1465 buf2.append("\" %>"); 1466 buf2.append(buf); 1467 content = buf2.toString(); 1468 } 1469 return content; 1470 } 1471 1472 /** 1473 * Parses the JSP content for includes and replaces all OpenCms VFS 1474 * path information with information for the real FS.<p> 1475 * 1476 * @param content the JSP content to parse 1477 * @param controller the current JSP controller 1478 * @param updatedFiles a set of already updated files 1479 * 1480 * @return the parsed JSP content 1481 */ 1482 protected String parseJspIncludes(String content, CmsFlexController controller, Set<String> updatedFiles) { 1483 1484 // check if a JSP directive occurs in the file 1485 int i1 = content.indexOf(DIRECTIVE_START); 1486 if (i1 < 0) { 1487 // no directive occurs 1488 return content; 1489 } 1490 1491 StringBuffer buf = new StringBuffer(content.length()); 1492 int p0 = 0, i2 = 0, slen = DIRECTIVE_START.length(); 1493 1494 while (i1 >= 0) { 1495 // parse the file and replace JSP filename references 1496 i2 = content.indexOf(DIRECTIVE_END, i1 + slen); 1497 if (i2 < 0) { 1498 // wrong syntax (missing end directive) - let the JSP compiler produce the error message 1499 return content; 1500 } else if (i2 > i1) { 1501 String directive = content.substring(i1 + slen, i2); 1502 if (LOG.isDebugEnabled()) { 1503 LOG.debug( 1504 Messages.get().getBundle().key( 1505 Messages.LOG_DIRECTIVE_DETECTED_3, 1506 DIRECTIVE_START, 1507 directive, 1508 DIRECTIVE_END)); 1509 } 1510 1511 int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; 1512 while (directive.charAt(t1) == ' ') { 1513 t1++; 1514 } 1515 String argument = null; 1516 if (directive.startsWith("include", t1)) { 1517 if (LOG.isDebugEnabled()) { 1518 LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "include")); 1519 } 1520 t2 = directive.indexOf("file", t1 + 7); 1521 t5 = 6; 1522 } else if (directive.startsWith("page", t1)) { 1523 if (LOG.isDebugEnabled()) { 1524 LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "page")); 1525 } 1526 t2 = directive.indexOf("errorPage", t1 + 4); 1527 t5 = 11; 1528 } 1529 1530 if (t2 > 0) { 1531 String sub = directive.substring(t2 + t5); 1532 char c1 = sub.charAt(t3); 1533 while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) { 1534 c1 = sub.charAt(++t3); 1535 } 1536 t4 = t3; 1537 while (c1 != '"') { 1538 c1 = sub.charAt(++t4); 1539 } 1540 if (t4 > t3) { 1541 argument = sub.substring(t3, t4); 1542 } 1543 if (LOG.isDebugEnabled()) { 1544 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DIRECTIVE_ARG_1, argument)); 1545 } 1546 } 1547 1548 if (argument != null) { 1549 // a file was found, changes have to be made 1550 String pre = directive.substring(0, t2 + t3 + t5); 1551 String suf = directive.substring(t2 + t3 + t5 + argument.length()); 1552 // now try to update the referenced file 1553 String jspname = updateJsp(argument, controller, updatedFiles); 1554 if (jspname != null) { 1555 // only change something in case no error had occurred 1556 directive = pre + jspname + suf; 1557 if (LOG.isDebugEnabled()) { 1558 LOG.debug( 1559 Messages.get().getBundle().key( 1560 Messages.LOG_DIRECTIVE_CHANGED_3, 1561 DIRECTIVE_START, 1562 directive, 1563 DIRECTIVE_END)); 1564 } 1565 } 1566 } 1567 1568 buf.append(content.substring(p0, i1 + slen)); 1569 buf.append(directive); 1570 p0 = i2; 1571 i1 = content.indexOf(DIRECTIVE_START, p0); 1572 } 1573 } 1574 if (i2 > 0) { 1575 // the content of the JSP was changed 1576 buf.append(content.substring(p0, content.length())); 1577 content = buf.toString(); 1578 } 1579 return content; 1580 } 1581 1582 /** 1583 * Parses all jsp link macros, and replace them by the right target path.<p> 1584 * 1585 * @param content the content to parse 1586 * @param controller the request controller 1587 * 1588 * @return the parsed content 1589 */ 1590 protected String parseJspLinkMacros(String content, CmsFlexController controller) { 1591 1592 CmsJspLinkMacroResolver macroResolver = new CmsJspLinkMacroResolver(controller.getCmsObject(), null, true); 1593 return macroResolver.resolveMacros(content); 1594 } 1595 1596 /** 1597 * Returns the jsp resource identified by the given name, using the controllers cms context.<p> 1598 * 1599 * @param controller the flex controller 1600 * @param jspName the name of the jsp 1601 * 1602 * @return an OpenCms resource 1603 * 1604 * @throws CmsException if something goes wrong 1605 */ 1606 protected CmsResource readJspResource(CmsFlexController controller, String jspName) throws CmsException { 1607 1608 // create an OpenCms user context that operates in the root site 1609 CmsObject cms = OpenCms.initCmsObject(controller.getCmsObject()); 1610 // we only need to change the site, but not the project, 1611 // since the request has already the right project set 1612 cms.getRequestContext().setSiteRoot(""); 1613 // try to read the resource 1614 return cms.readResource(jspName); 1615 } 1616 1617 /** 1618 * Delivers the plain uninterpreted resource with escaped XML.<p> 1619 * 1620 * This is intended for viewing historical versions.<p> 1621 * 1622 * @param cms the initialized CmsObject which provides user permissions 1623 * @param file the requested OpenCms VFS resource 1624 * @param req the servlet request 1625 * @param res the servlet response 1626 * 1627 * @throws IOException might be thrown by the servlet environment 1628 * @throws CmsException in case of errors accessing OpenCms functions 1629 */ 1630 protected void showSource(CmsObject cms, CmsResource file, HttpServletRequest req, HttpServletResponse res) 1631 throws CmsException, IOException { 1632 1633 CmsResource historyResource = (CmsResource)CmsHistoryResourceHandler.getHistoryResource(req); 1634 if (historyResource == null) { 1635 historyResource = file; 1636 } 1637 CmsFile historyFile = cms.readFile(historyResource); 1638 String content = new String(historyFile.getContents()); 1639 // change the content-type header so that browsers show plain text 1640 res.setContentLength(content.length()); 1641 res.setContentType("text/plain"); 1642 1643 Writer out = res.getWriter(); 1644 out.write(content); 1645 out.close(); 1646 } 1647 1648 /** 1649 * Updates a JSP page in the "real" file system in case the VFS resource has changed based on the resource name.<p> 1650 * 1651 * Generates a resource based on the provided name and calls {@link #updateJsp(CmsResource, CmsFlexController, Set)}.<p> 1652 * 1653 * @param vfsName the name of the JSP file resource in the VFS 1654 * @param controller the controller for the JSP integration 1655 * @param updatedFiles a Set containing all JSP pages that have been already updated 1656 * 1657 * @return the file name of the updated JSP in the "real" FS 1658 */ 1659 protected String updateJsp(String vfsName, CmsFlexController controller, Set<String> updatedFiles) { 1660 1661 String jspVfsName = CmsLinkManager.getAbsoluteUri(vfsName, controller.getCurrentRequest().getElementRootPath()); 1662 if (LOG.isDebugEnabled()) { 1663 LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_JSP_1, jspVfsName)); 1664 } 1665 String jspRfsName; 1666 try { 1667 CmsResource includeResource; 1668 try { 1669 // first try a root path 1670 includeResource = readJspResource(controller, jspVfsName); 1671 } catch (CmsVfsResourceNotFoundException e) { 1672 // if fails, try a site relative path 1673 includeResource = readJspResource( 1674 controller, 1675 controller.getCmsObject().getRequestContext().addSiteRoot(jspVfsName)); 1676 } 1677 // make sure the jsp referenced file is generated 1678 jspRfsName = updateJsp(includeResource, controller, updatedFiles); 1679 if (LOG.isDebugEnabled()) { 1680 LOG.debug(Messages.get().getBundle().key(Messages.LOG_NAME_REAL_FS_1, jspRfsName)); 1681 } 1682 } catch (Exception e) { 1683 jspRfsName = null; 1684 if (LOG.isDebugEnabled()) { 1685 LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_UPDATE_1, jspVfsName), e); 1686 } 1687 } 1688 return jspRfsName; 1689 } 1690 1691 /** 1692 * Updates all jsp files that include the given jsp file using the 'link.strong' macro.<p> 1693 * 1694 * @param resource the current updated jsp file 1695 * @param controller the controller for the jsp integration 1696 * @param updatedFiles the already updated files 1697 * 1698 * @return <code>true</code> if the given JSP file should be updated due to dirty included files 1699 * 1700 * @throws ServletException might be thrown in the process of including the JSP 1701 * @throws IOException might be thrown in the process of including the JSP 1702 * @throws CmsLoaderException if the resource type can not be read 1703 */ 1704 protected boolean updateStrongLinks(CmsResource resource, CmsFlexController controller, Set<String> updatedFiles) 1705 throws CmsLoaderException, IOException, ServletException { 1706 1707 int numberOfUpdates = updatedFiles.size(); 1708 CmsObject cms = controller.getCmsObject(); 1709 CmsRelationFilter filter = CmsRelationFilter.TARGETS.filterType(CmsRelationType.JSP_STRONG); 1710 Iterator<CmsRelation> it; 1711 try { 1712 it = cms.getRelationsForResource(resource, filter).iterator(); 1713 } catch (CmsException e) { 1714 // should never happen 1715 if (LOG.isErrorEnabled()) { 1716 LOG.error(e.getLocalizedMessage(), e); 1717 } 1718 return false; 1719 } 1720 while (it.hasNext()) { 1721 CmsRelation relation = it.next(); 1722 CmsResource target = null; 1723 try { 1724 target = relation.getTarget(cms, CmsResourceFilter.DEFAULT); 1725 } catch (CmsException e) { 1726 // should never happen 1727 if (LOG.isErrorEnabled()) { 1728 LOG.error(e.getLocalizedMessage(), e); 1729 } 1730 continue; 1731 } 1732 // prevent recursive update when including the same file 1733 if (resource.equals(target)) { 1734 continue; 1735 } 1736 // update the target 1737 updateJsp(target, controller, updatedFiles); 1738 } 1739 // the current jsp file should be updated only if one of the included jsp has been updated 1740 return numberOfUpdates < updatedFiles.size(); 1741 } 1742 1743 /** 1744 * Returns the read-write-lock for the given jsp vfs name.<p> 1745 * 1746 * @param jspVfsName the jsp vfs name 1747 * 1748 * @return the read-write-lock 1749 */ 1750 private ReentrantReadWriteLock getFileLock(String jspVfsName) { 1751 1752 ReentrantReadWriteLock lock = m_fileLocks.get(jspVfsName); 1753 if (lock == null) { 1754 synchronized (m_fileLocks) { 1755 if (!m_fileLocks.containsKey(jspVfsName)) { 1756 m_fileLocks.put(jspVfsName, new ReentrantReadWriteLock(true)); 1757 } 1758 lock = m_fileLocks.get(jspVfsName); 1759 } 1760 } 1761 return lock; 1762 } 1763 1764 /** 1765 * Returns the RFS path for a JSP resource.<p> 1766 * 1767 * This does not check whether there actually exists a file at the returned path. 1768 * 1769 * @param resource the JSP resource 1770 * @param online true if the path for the online project should be returned 1771 * 1772 * @return the RFS path for the JSP 1773 * 1774 * @throws CmsLoaderException if accessing the resource loader fails 1775 */ 1776 private String getJspRfsPath(CmsResource resource, boolean online) throws CmsLoaderException { 1777 1778 String jspVfsName = resource.getRootPath(); 1779 String extension; 1780 int loaderId = OpenCms.getResourceManager().getResourceType(resource.getTypeId()).getLoaderId(); 1781 if ((loaderId == CmsJspLoader.RESOURCE_LOADER_ID) && (!jspVfsName.endsWith(JSP_EXTENSION))) { 1782 // this is a true JSP resource that does not end with ".jsp" 1783 extension = JSP_EXTENSION; 1784 } else { 1785 // not a JSP resource or already ends with ".jsp" 1786 extension = ""; 1787 } 1788 String jspPath = CmsFileUtil.getRepositoryName(m_jspRepository, jspVfsName + extension, online); 1789 return jspPath; 1790 } 1791}