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