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