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