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