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