/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.opencms.loader; import org.opencms.configuration.CmsParameterConfiguration; import org.opencms.file.CmsFile; import org.opencms.file.CmsObject; import org.opencms.file.CmsPropertyDefinition; import org.opencms.file.CmsRequestContext; import org.opencms.file.CmsResource; import org.opencms.file.CmsResourceFilter; import org.opencms.file.CmsVfsResourceNotFoundException; import org.opencms.file.history.CmsHistoryResourceHandler; import org.opencms.flex.CmsFlexCache; import org.opencms.flex.CmsFlexController; import org.opencms.flex.CmsFlexRequest; import org.opencms.flex.CmsFlexResponse; import org.opencms.i18n.CmsEncoder; import org.opencms.i18n.CmsMessageContainer; import org.opencms.jsp.util.CmsJspLinkMacroResolver; import org.opencms.main.CmsEvent; import org.opencms.main.CmsException; import org.opencms.main.CmsLog; import org.opencms.main.I_CmsEventListener; import org.opencms.main.OpenCms; import org.opencms.relations.CmsRelation; import org.opencms.relations.CmsRelationFilter; import org.opencms.relations.CmsRelationType; import org.opencms.staticexport.CmsLinkManager; import org.opencms.util.CmsCollectionsGenericWrapper; import org.opencms.util.CmsFileUtil; import org.opencms.util.CmsRequestUtil; import org.opencms.util.CmsStringUtil; import org.opencms.util.I_CmsRegexSubstitution; import org.opencms.workplace.CmsWorkplaceManager; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.SocketException; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.collections.map.LRUMap; import org.apache.commons.logging.Log; import com.google.common.base.Splitter; import com.google.common.collect.Maps; /** * The JSP loader which enables the execution of JSP in OpenCms.<p> * * Parameters supported by this loader:<dl> * * <dt>jsp.repository</dt><dd> * (Optional) This is the root directory in the "real" file system where generated JSPs are stored. * The default is the web application path, e.g. in Tomcat if your web application is * names "opencms" it would be <code>${TOMCAT_HOME}/webapps/opencms/</code>. * The <code>jsp.folder</code> (see below) is added to this path. * Usually the <code>jsp.repository</code> is not changed. * </dd> * * <dt>jsp.folder</dt><dd> * (Optional) A path relative to the <code>jsp.repository</code> path where the * JSPs generated by OpenCms are stored. The default is to store the generated JSP in * <code>/WEB-INF/jsp/</code>. * This works well in Tomcat 4, and the JSPs are * not accessible directly from the outside this way, only through the OpenCms servlet. * <i>Please note:</i> Some servlet environments (e.g. BEA Weblogic) do not permit * JSPs to be stored under <code>/WEB-INF</code>. For environments like these, * set the path to some place where JSPs can be accessed, e.g. <code>/jsp/</code> only. * </dd> * * <dt>jsp.errorpage.committed</dt><dd> * (Optional) This parameter controls behavior of JSP error pages * i.e. <code><% page errorPage="..." %></code>. If you find that these don't work * in your servlet environment, you should try to change the value here. * The default <code>true</code> has been tested with Tomcat 4.1 and 5.0. * Older versions of Tomcat like 4.0 require a setting of <code>false</code>.</dd> * </dl> * * @since 6.0.0 * * @see I_CmsResourceLoader */ public class CmsJspLoader implements I_CmsResourceLoader, I_CmsFlexCacheEnabledLoader, I_CmsEventListener { /** Property value for "cache" that indicates that the FlexCache should be bypassed. */ public static final String CACHE_PROPERTY_BYPASS = "bypass"; /** Property value for "cache" that indicates that the output should be streamed. */ public static final String CACHE_PROPERTY_STREAM = "stream"; /** Default jsp folder constant. */ public static final String DEFAULT_JSP_FOLDER = "/WEB-INF/jsp/"; /** Special JSP directive tag start (<code>%></code>). */ public static final String DIRECTIVE_END = "%>"; /** Special JSP directive tag start (<code><%(</code>). */ public static final String DIRECTIVE_START = "<%@"; /** Extension for JSP managed by OpenCms (<code>.jsp</code>). */ public static final String JSP_EXTENSION = ".jsp"; /** Cache max age parameter name. */ public static final String PARAM_CLIENT_CACHE_MAXAGE = "client.cache.maxage"; /** Jsp cache size parameter name. */ public static final String PARAM_JSP_CACHE_SIZE = "jsp.cache.size"; /** Error page committed parameter name. */ public static final String PARAM_JSP_ERRORPAGE_COMMITTED = "jsp.errorpage.committed"; /** Jsp folder parameter name. */ public static final String PARAM_JSP_FOLDER = "jsp.folder"; /** Jsp repository parameter name. */ public static final String PARAM_JSP_REPOSITORY = "jsp.repository"; /** The id of this loader. */ public static final int RESOURCE_LOADER_ID = 6; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsJspLoader.class); /** The maximum age for delivered contents in the clients cache. */ private static long m_clientCacheMaxAge; /** The directory to store the generated JSP pages in (absolute path). */ private static String m_jspRepository; /** The directory to store the generated JSP pages in (relative path in web application). */ private static String m_jspWebAppRepository; /** Read write locks for jsp files. */ @SuppressWarnings("unchecked") private static Map<String, ReentrantReadWriteLock> m_fileLocks = new LRUMap(10000); /** The CmsFlexCache used to store generated cache entries in. */ private CmsFlexCache m_cache; /** The resource loader configuration. */ private CmsParameterConfiguration m_configuration; /** Flag to indicate if error pages are marked as "committed". */ private boolean m_errorPagesAreNotCommitted; /** The cached offline JSPs. */ private Map<String, Boolean> m_offlineJsps; /** The cached online JSPs. */ private Map<String, Boolean> m_onlineJsps; /** A map from taglib names to their URIs. */ private Map<String, String> m_taglibs = Maps.newHashMap(); /** * The constructor of the class is empty, the initial instance will be * created by the resource manager upon startup of OpenCms.<p> * * @see org.opencms.loader.CmsResourceManager */ public CmsJspLoader() { m_configuration = new CmsParameterConfiguration(); OpenCms.addCmsEventListener(this, new int[] { EVENT_CLEAR_CACHES, EVENT_CLEAR_OFFLINE_CACHES, EVENT_CLEAR_ONLINE_CACHES}); initCaches(1000); } /** * Returns the absolute path in the "real" file system for the JSP repository * toplevel directory.<p> * * @return The full path to the JSP repository */ public String getJspRepository() { return m_jspRepository; } /** * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) */ public void addConfigurationParameter(String paramName, String paramValue) { m_configuration.add(paramName, paramValue); if (paramName.startsWith("taglib.")) { m_taglibs.put(paramName.replaceFirst("^taglib\\.", ""), paramValue.trim()); } } /** * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent) */ public void cmsEvent(CmsEvent event) { switch (event.getType()) { case EVENT_CLEAR_CACHES: m_offlineJsps.clear(); m_onlineJsps.clear(); return; case EVENT_CLEAR_OFFLINE_CACHES: m_offlineJsps.clear(); return; case EVENT_CLEAR_ONLINE_CACHES: m_onlineJsps.clear(); return; default: // do nothing } } /** * Destroy this ResourceLoder, this is a NOOP so far. */ public void destroy() { // NOOP } /** * @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) */ public byte[] dump( CmsObject cms, CmsResource file, String element, Locale locale, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // get the current Flex controller CmsFlexController controller = CmsFlexController.getController(req); CmsFlexController oldController = null; if (controller != null) { // for dumping we must create an new "top level" controller, save the old one to be restored later oldController = controller; } byte[] result = null; try { // now create a new, temporary Flex controller controller = getController(cms, file, req, res, false, false); if (element != null) { // add the element parameter to the included request String[] value = new String[] {element}; Map<String, String[]> parameters = Collections.singletonMap( I_CmsResourceLoader.PARAMETER_ELEMENT, value); controller.getCurrentRequest().addParameterMap(parameters); } controller.getCurrentRequest().addAttributeMap(CmsRequestUtil.getAtrributeMap(req)); // dispatch to the JSP result = dispatchJsp(controller); // remove temporary controller CmsFlexController.removeController(req); } finally { if ((oldController != null) && (controller != null)) { // update "date last modified" oldController.updateDates(controller.getDateLastModified(), controller.getDateExpires()); // reset saved controller CmsFlexController.setController(req, oldController); } } return result; } /** * @see org.opencms.loader.I_CmsResourceLoader#export(org.opencms.file.CmsObject, org.opencms.file.CmsResource, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public byte[] export(CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // get the Flex controller CmsFlexController controller = getController(cms, resource, req, res, false, true); // dispatch to the JSP byte[] result = dispatchJsp(controller); // remove the controller from the request CmsFlexController.removeController(req); // return the contents return result; } /** * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() */ public CmsParameterConfiguration getConfiguration() { // return the configuration in an immutable form return m_configuration; } /** * @see org.opencms.loader.I_CmsResourceLoader#getLoaderId() */ public int getLoaderId() { return RESOURCE_LOADER_ID; } /** * Returns a set of root paths of files that are including the given resource using the 'link.strong' macro.<p> * * @param cms the current cms context * @param resource the resource to check * @param referencingPaths the set of already referencing paths, also return parameter * * @throws CmsException if something goes wrong */ public void getReferencingStrongLinks(CmsObject cms, CmsResource resource, Set<String> referencingPaths) throws CmsException { CmsRelationFilter filter = CmsRelationFilter.SOURCES.filterType(CmsRelationType.JSP_STRONG); Iterator<CmsRelation> it = cms.getRelationsForResource(resource, filter).iterator(); while (it.hasNext()) { CmsRelation relation = it.next(); try { CmsResource source = relation.getSource(cms, CmsResourceFilter.DEFAULT); // check if file was already included if (referencingPaths.contains(source.getRootPath())) { // no need to include this file more than once continue; } referencingPaths.add(source.getRootPath()); getReferencingStrongLinks(cms, source, referencingPaths); } catch (CmsException e) { if (LOG.isErrorEnabled()) { LOG.error(e.getLocalizedMessage(), e); } } } } /** * Return a String describing the ResourceLoader, * which is (localized to the system default locale) * <code>"The OpenCms default resource loader for JSP"</code>.<p> * * @return a describing String for the ResourceLoader */ public String getResourceLoaderInfo() { return Messages.get().getBundle().key(Messages.GUI_LOADER_JSP_DEFAULT_DESC_0); } /** * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() */ public void initConfiguration() { m_jspRepository = m_configuration.get(PARAM_JSP_REPOSITORY); if (m_jspRepository == null) { m_jspRepository = OpenCms.getSystemInfo().getWebApplicationRfsPath(); } m_jspWebAppRepository = m_configuration.getString(PARAM_JSP_FOLDER, DEFAULT_JSP_FOLDER); if (!m_jspWebAppRepository.endsWith("/")) { m_jspWebAppRepository += "/"; } m_jspRepository = CmsFileUtil.normalizePath(m_jspRepository + m_jspWebAppRepository); String maxAge = m_configuration.get(PARAM_CLIENT_CACHE_MAXAGE); if (maxAge == null) { m_clientCacheMaxAge = -1; } else { m_clientCacheMaxAge = Long.parseLong(maxAge); } // get the "error pages are committed or not" flag from the configuration m_errorPagesAreNotCommitted = m_configuration.getBoolean(PARAM_JSP_ERRORPAGE_COMMITTED, true); int cacheSize = m_configuration.getInteger(PARAM_JSP_CACHE_SIZE, -1); if (cacheSize > 0) { initCaches(cacheSize); } // output setup information if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_JSP_REPOSITORY_ABS_PATH_1, m_jspRepository)); CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_WEBAPP_PATH_1, m_jspWebAppRepository)); CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_JSP_REPOSITORY_ERR_PAGE_COMMOTED_1, Boolean.valueOf(m_errorPagesAreNotCommitted))); if (m_clientCacheMaxAge > 0) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_CLIENT_CACHE_MAX_AGE_1, maxAge)); } if (cacheSize > 0) { CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_JSP_CACHE_SIZE_1, String.valueOf(cacheSize))); } CmsLog.INIT.info(Messages.get().getBundle().key( Messages.INIT_LOADER_INITIALIZED_1, this.getClass().getName())); } } /** * @see org.opencms.loader.I_CmsResourceLoader#isStaticExportEnabled() */ public boolean isStaticExportEnabled() { return true; } /** * @see org.opencms.loader.I_CmsResourceLoader#isStaticExportProcessable() */ public boolean isStaticExportProcessable() { return true; } /** * @see org.opencms.loader.I_CmsResourceLoader#isUsableForTemplates() */ public boolean isUsableForTemplates() { return true; } /** * @see org.opencms.loader.I_CmsResourceLoader#isUsingUriWhenLoadingTemplate() */ public boolean isUsingUriWhenLoadingTemplate() { return false; } /** * @see org.opencms.loader.I_CmsResourceLoader#load(org.opencms.file.CmsObject, org.opencms.file.CmsResource, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public void load(CmsObject cms, CmsResource file, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException, CmsException { CmsRequestContext context = cms.getRequestContext(); // If we load template jsp or template-element jsp (xml contents or xml pages) don't show source (2nd test) if ((CmsHistoryResourceHandler.isHistoryRequest(req)) && (context.getUri().equals(context.removeSiteRoot(file.getRootPath())))) { showSource(cms, file, req, res); } else { // load and process the JSP boolean streaming = false; boolean bypass = false; // read "cache" property for requested VFS resource to check for special "stream" and "bypass" values String cacheProperty = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CACHE, true).getValue(); if (cacheProperty != null) { cacheProperty = cacheProperty.trim(); if (CACHE_PROPERTY_STREAM.equals(cacheProperty)) { streaming = true; } else if (CACHE_PROPERTY_BYPASS.equals(cacheProperty)) { streaming = true; bypass = true; } } // get the Flex controller CmsFlexController controller = getController(cms, file, req, res, streaming, true); if (bypass || controller.isForwardMode()) { // once in forward mode, always in forward mode (for this request) controller.setForwardMode(true); // bypass Flex cache for this page, update the JSP first if necessary String target = updateJsp(file, controller, new HashSet<String>()); // dispatch to external JSP req.getRequestDispatcher(target).forward(controller.getCurrentRequest(), res); } else { // Flex cache not bypassed, dispatch to internal JSP dispatchJsp(controller); } // remove the controller from the request if not forwarding if (!controller.isForwardMode()) { CmsFlexController.removeController(req); } } } /** * Replaces taglib attributes in page directives with taglib directives.<p> * * @param content the JSP source text * * @return the transformed JSP text */ public String processTaglibAttributes(String content) { // matches a whole page directive final Pattern directivePattern = Pattern.compile("(?sm)<%@\\s*page.*?%>"); // matches a taglibs attribute and captures its values final Pattern taglibPattern = Pattern.compile("(?sm)taglibs\\s*=\\s*\"(.*?)\""); final Pattern commaPattern = Pattern.compile("(?sm)\\s*,\\s*"); final Set<String> taglibs = new LinkedHashSet<String>(); // we insert the marker after the first page directive final String marker = ":::TAGLIBS:::"; I_CmsRegexSubstitution directiveSub = new I_CmsRegexSubstitution() { private boolean m_first = true; public String substituteMatch(String string, Matcher matcher) { String match = string.substring(matcher.start(), matcher.end()); I_CmsRegexSubstitution taglibSub = new I_CmsRegexSubstitution() { public String substituteMatch(String string1, Matcher matcher1) { // values of the taglibs attribute String match1 = string1.substring(matcher1.start(1), matcher1.end(1)); for (String taglibKey : Splitter.on(commaPattern).split(match1)) { taglibs.add(taglibKey); } return ""; } }; String result = CmsStringUtil.substitute(taglibPattern, match, taglibSub); if (m_first) { result += marker; m_first = false; } return result; } }; String substituted = CmsStringUtil.substitute(directivePattern, content, directiveSub); // insert taglib inclusion substituted = substituted.replaceAll(marker, generateTaglibInclusions(taglibs)); // remove empty page directives substituted = substituted.replaceAll("(?sm)<%@\\s*page\\s*%>", ""); return substituted; } /** * Removes the given resources from the cache.<p> * * @param rootPaths the set of root paths to remove * @param online if online or offline */ public void removeFromCache(Set<String> rootPaths, boolean online) { Map<String, Boolean> cache; if (online) { cache = m_onlineJsps; } else { cache = m_offlineJsps; } Iterator<String> itRemove = rootPaths.iterator(); while (itRemove.hasNext()) { String rootPath = itRemove.next(); cache.remove(rootPath); } } /** * @see org.opencms.loader.I_CmsResourceLoader#service(org.opencms.file.CmsObject, org.opencms.file.CmsResource, javax.servlet.ServletRequest, javax.servlet.ServletResponse) */ public void service(CmsObject cms, CmsResource resource, ServletRequest req, ServletResponse res) throws ServletException, IOException, CmsLoaderException { CmsFlexController controller = CmsFlexController.getController(req); // get JSP target name on "real" file system String target = updateJsp(resource, controller, new HashSet<String>(8)); // important: Indicate that all output must be buffered controller.getCurrentResponse().setOnlyBuffering(true); // dispatch to external file controller.getCurrentRequest().getRequestDispatcherToExternal(cms.getSitePath(resource), target).include( req, res); } /** * @see org.opencms.loader.I_CmsFlexCacheEnabledLoader#setFlexCache(org.opencms.flex.CmsFlexCache) */ public void setFlexCache(CmsFlexCache cache) { m_cache = cache; // output setup information if (CmsLog.INIT.isInfoEnabled()) { CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ADD_FLEX_CACHE_0)); } } /** * Updates a JSP page in the "real" file system in case the VFS resource has changed.<p> * * Also processes the <code><%@ cms %></code> tags before the JSP is written to the real FS. * Also recursively updates all files that are referenced by a <code><%@ cms %></code> tag * on this page to make sure the file actually exists in the real FS. * All <code><%@ include %></code> tags are parsed and the name in the tag is translated * from the OpenCms VFS path to the path in the real FS. * The same is done for filenames in <code><%@ page errorPage=... %></code> tags.<p> * * @param resource the requested JSP file resource in the VFS * @param controller the controller for the JSP integration * @param updatedFiles a Set containing all JSP pages that have been already updated * * @return the file name of the updated JSP in the "real" FS * * @throws ServletException might be thrown in the process of including the JSP * @throws IOException might be thrown in the process of including the JSP * @throws CmsLoaderException if the resource type can not be read */ public String updateJsp(CmsResource resource, CmsFlexController controller, Set<String> updatedFiles) throws IOException, ServletException, CmsLoaderException { String jspVfsName = resource.getRootPath(); String extension; boolean isHardInclude; int loaderId = OpenCms.getResourceManager().getResourceType(resource.getTypeId()).getLoaderId(); if ((loaderId == CmsJspLoader.RESOURCE_LOADER_ID) && (!jspVfsName.endsWith(JSP_EXTENSION))) { // this is a true JSP resource that does not end with ".jsp" extension = JSP_EXTENSION; isHardInclude = false; } else { // not a JSP resource or already ends with ".jsp" extension = ""; // if this is a JSP we don't treat it as hard include isHardInclude = (loaderId != CmsJspLoader.RESOURCE_LOADER_ID); } String jspTargetName = CmsFileUtil.getRepositoryName( m_jspWebAppRepository, jspVfsName + extension, controller.getCurrentRequest().isOnline()); // check if page was already updated if (updatedFiles.contains(jspTargetName)) { // no need to write the already included file to the real FS more then once return jspTargetName; } String jspPath = CmsFileUtil.getRepositoryName( m_jspRepository, jspVfsName + extension, controller.getCurrentRequest().isOnline()); File d = new File(jspPath).getParentFile(); if ((d == null) || (d.exists() && !(d.isDirectory() && d.canRead()))) { CmsMessageContainer message = Messages.get().container(Messages.LOG_ACCESS_DENIED_1, jspPath); LOG.error(message.key()); // can not continue throw new ServletException(message.key()); } if (!d.exists()) { // create directory structure d.mkdirs(); } ReentrantReadWriteLock readWriteLock = getFileLock(jspVfsName); try { // get a read lock for this jsp readWriteLock.readLock().lock(); File jspFile = new File(jspPath); // check if the JSP must be updated boolean mustUpdate = false; long jspModificationDate = 0; if (!jspFile.exists()) { // file does not exist in real FS mustUpdate = true; // make sure the parent folder exists File folder = jspFile.getParentFile(); if (!folder.exists()) { boolean success = folder.mkdirs(); if (!success) { LOG.error(org.opencms.db.Messages.get().getBundle().key( org.opencms.db.Messages.LOG_CREATE_FOLDER_FAILED_1, folder.getAbsolutePath())); } } } else { jspModificationDate = jspFile.lastModified(); if (jspModificationDate <= resource.getDateLastModified()) { // file in real FS is older then file in VFS mustUpdate = true; } else if (controller.getCurrentRequest().isDoRecompile()) { // recompile is forced with parameter mustUpdate = true; } else { // check if update is needed if (controller.getCurrentRequest().isOnline()) { mustUpdate = !m_onlineJsps.containsKey(jspVfsName); } else { mustUpdate = !m_offlineJsps.containsKey(jspVfsName); } // check strong links only if update is needed if (mustUpdate) { // update strong link dependencies mustUpdate = updateStrongLinks(resource, controller, updatedFiles); } } } if (mustUpdate) { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_WRITING_JSP_1, jspTargetName)); } // jsp needs updating, acquire a write lock readWriteLock.readLock().unlock(); readWriteLock.writeLock().lock(); try { // check again if updating is still necessary as this might have happened while waiting for the write lock if (!jspFile.exists() || (jspModificationDate == jspFile.lastModified())) { updatedFiles.add(jspTargetName); byte[] contents; String encoding; try { CmsObject cms = controller.getCmsObject(); contents = cms.readFile(resource).getContents(); // check the "content-encoding" property for the JSP, use system default if not found on path encoding = cms.readPropertyObject( resource, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue(); if (encoding == null) { encoding = OpenCms.getSystemInfo().getDefaultEncoding(); } else { encoding = CmsEncoder.lookupEncoding(encoding.trim(), encoding); } } catch (CmsException e) { controller.setThrowable(e, jspVfsName); throw new ServletException(Messages.get().getBundle().key( Messages.ERR_LOADER_JSP_ACCESS_1, jspVfsName), e); } try { // parse the JSP and modify OpenCms critical directives contents = parseJsp(contents, encoding, controller, updatedFiles, isHardInclude); if (LOG.isInfoEnabled()) { // check for existing file and display some debug info LOG.info(Messages.get().getBundle().key( Messages.LOG_JSP_PERMCHECK_4, new Object[] { jspFile.getAbsolutePath(), Boolean.valueOf(jspFile.exists()), Boolean.valueOf(jspFile.isFile()), Boolean.valueOf(jspFile.canWrite())})); } // write the parsed JSP content to the real FS synchronized (CmsJspLoader.class) { // this must be done only one file at a time FileOutputStream fs = new FileOutputStream(jspFile); fs.write(contents); fs.close(); } if (controller.getCurrentRequest().isOnline()) { m_onlineJsps.put(jspVfsName, Boolean.TRUE); } else { m_offlineJsps.put(jspVfsName, Boolean.TRUE); } if (LOG.isInfoEnabled()) { LOG.info(Messages.get().getBundle().key( Messages.LOG_UPDATED_JSP_2, jspTargetName, jspVfsName)); } } catch (FileNotFoundException e) { throw new ServletException(Messages.get().getBundle().key( Messages.ERR_LOADER_JSP_WRITE_1, jspFile.getName()), e); } } } finally { readWriteLock.readLock().lock(); readWriteLock.writeLock().unlock(); } } // update "last modified" and "expires" date on controller controller.updateDates(jspFile.lastModified(), CmsResource.DATE_EXPIRED_DEFAULT); } finally { //m_processingFiles.remove(jspVfsName); readWriteLock.readLock().unlock(); } return jspTargetName; } /** * Updates the internal jsp repository when the servlet container * tries to compile a jsp file that may not exist.<p> * * @param servletPath the servlet path, just to avoid unneeded recursive calls * @param request the current request */ public void updateJspFromRequest(String servletPath, CmsFlexRequest request) { // assemble the RFS name of the requested jsp String jspUri = servletPath; String pathInfo = request.getPathInfo(); if (pathInfo != null) { jspUri += pathInfo; } // check the file name if ((jspUri == null) || !jspUri.startsWith(m_jspWebAppRepository)) { // nothing to do, this kind of request are handled by the CmsJspLoader#service method return; } // remove prefixes jspUri = jspUri.substring(m_jspWebAppRepository.length()); if (jspUri.startsWith(CmsFlexCache.REPOSITORY_ONLINE)) { jspUri = jspUri.substring(CmsFlexCache.REPOSITORY_ONLINE.length()); } else if (jspUri.startsWith(CmsFlexCache.REPOSITORY_OFFLINE)) { jspUri = jspUri.substring(CmsFlexCache.REPOSITORY_OFFLINE.length()); } else { // this is not an OpenCms jsp file return; } // read the resource from OpenCms CmsFlexController controller = CmsFlexController.getController(request); try { CmsResource includeResource; try { // first try to read the resource assuming no additional jsp extension was needed includeResource = readJspResource(controller, jspUri); } catch (CmsVfsResourceNotFoundException e) { // try removing the additional jsp extension if (jspUri.endsWith(JSP_EXTENSION)) { jspUri = jspUri.substring(0, jspUri.length() - JSP_EXTENSION.length()); } includeResource = readJspResource(controller, jspUri); } // make sure the jsp referenced file is generated updateJsp(includeResource, controller, new HashSet<String>(8)); } catch (Exception e) { if (LOG.isDebugEnabled()) { LOG.debug(e.getLocalizedMessage(), e); } } } /** * Dispatches the current request to the OpenCms internal JSP.<p> * * @param controller the current controller * * @return the content of the processed JSP * * @throws ServletException if inclusion does not work * @throws IOException if inclusion does not work */ protected byte[] dispatchJsp(CmsFlexController controller) throws ServletException, IOException { // get request / response wrappers CmsFlexRequest f_req = controller.getCurrentRequest(); CmsFlexResponse f_res = controller.getCurrentResponse(); try { f_req.getRequestDispatcher(controller.getCmsObject().getSitePath(controller.getCmsResource())).include( f_req, f_res); } catch (SocketException e) { // uncritical, might happen if client (browser) does not wait until end of page delivery LOG.debug(Messages.get().getBundle().key(Messages.LOG_IGNORING_EXC_1, e.getClass().getName()), e); } byte[] result = null; HttpServletResponse res = controller.getTopResponse(); if (!controller.isStreaming() && !f_res.isSuspended()) { try { // if a JSP error page was triggered the response will be already committed here if (!res.isCommitted() || m_errorPagesAreNotCommitted) { // check if the current request was done by a workplace user boolean isWorkplaceUser = CmsWorkplaceManager.isWorkplaceUser(f_req); // check if the content was modified since the last request if (controller.isTop() && !isWorkplaceUser && CmsFlexController.isNotModifiedSince(f_req, controller.getDateLastModified())) { if (f_req.getParameterMap().size() == 0) { // only use "expires" header on pages that have no parameters, // otherwise some browsers (e.g. IE 6) will not even try to request // updated versions of the page CmsFlexController.setDateExpiresHeader( res, controller.getDateExpires(), m_clientCacheMaxAge); } res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return null; } // get the result byte array result = f_res.getWriterBytes(); HttpServletRequest req = controller.getTopRequest(); if (req.getHeader(CmsRequestUtil.HEADER_OPENCMS_EXPORT) != null) { // this is a non "on-demand" static export request, don't write to the response stream req.setAttribute( CmsRequestUtil.HEADER_OPENCMS_EXPORT, new Long(controller.getDateLastModified())); } else if (controller.isTop()) { // process headers and write output if this is the "top" request/response res.setContentLength(result.length); // check for preset error code Integer errorCode = (Integer)req.getAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE); if (errorCode == null) { // set last modified / no cache headers only if this is not an error page if (isWorkplaceUser) { res.setDateHeader(CmsRequestUtil.HEADER_LAST_MODIFIED, System.currentTimeMillis()); CmsRequestUtil.setNoCacheHeaders(res); } else { // set date last modified header CmsFlexController.setDateLastModifiedHeader(res, controller.getDateLastModified()); if ((f_req.getParameterMap().size() == 0) && (controller.getDateLastModified() > -1)) { // only use "expires" header on pages that have no parameters // and that are cachable (i.e. 'date last modified' is set) // otherwise some browsers (e.g. IE 6) will not even try to request // updated versions of the page CmsFlexController.setDateExpiresHeader( res, controller.getDateExpires(), m_clientCacheMaxAge); } } // set response status to "200 - OK" (required for static export "on-demand") res.setStatus(HttpServletResponse.SC_OK); } else { // set previously saved error code res.setStatus(errorCode.intValue()); } // process the headers CmsFlexResponse.processHeaders(f_res.getHeaders(), res); res.getOutputStream().write(result); res.getOutputStream().flush(); } } } catch (IllegalStateException e) { // uncritical, might happen if JSP error page was used LOG.debug(Messages.get().getBundle().key(Messages.LOG_IGNORING_EXC_1, e.getClass().getName()), e); } catch (SocketException e) { // uncritical, might happen if client (browser) does not wait until end of page delivery LOG.debug(Messages.get().getBundle().key(Messages.LOG_IGNORING_EXC_1, e.getClass().getName()), e); } } return result; } /** * Generates the taglib directives for a collection of taglib identifiers.<p> * * @param taglibs the taglib identifiers * * @return a string containing taglib directives */ protected String generateTaglibInclusions(Collection<String> taglibs) { StringBuffer buffer = new StringBuffer(); for (String taglib : taglibs) { String uri = m_taglibs.get(taglib); if (uri != null) { buffer.append("<%@ taglib prefix=\"" + taglib + "\" uri=\"" + uri + "\" %>"); } } return buffer.toString(); } /** * Delivers a Flex controller, either by creating a new one, or by re-using an existing one.<p> * * @param cms the initial CmsObject to wrap in the controller * @param resource the resource requested * @param req the current request * @param res the current response * @param streaming indicates if the response is streaming * @param top indicates if the response is the top response * * @return a Flex controller */ protected CmsFlexController getController( CmsObject cms, CmsResource resource, HttpServletRequest req, HttpServletResponse res, boolean streaming, boolean top) { CmsFlexController controller = null; if (top) { // only check for existing controller if this is the "top" request/response controller = CmsFlexController.getController(req); } if (controller == null) { // create new request / response wrappers controller = new CmsFlexController(cms, resource, m_cache, req, res, streaming, top); CmsFlexController.setController(req, controller); CmsFlexRequest f_req = new CmsFlexRequest(req, controller); CmsFlexResponse f_res = new CmsFlexResponse(res, controller, streaming, true); controller.push(f_req, f_res); } else if (controller.isForwardMode()) { // reset CmsObject (because of URI) if in forward mode controller = new CmsFlexController(cms, controller); CmsFlexController.setController(req, controller); } return controller; } /** * Initializes the caches.<p> * * @param cacheSize the cache size */ protected void initCaches(int cacheSize) { Map<String, Boolean> map = CmsCollectionsGenericWrapper.createLRUMap(cacheSize); m_offlineJsps = Collections.synchronizedMap(map); map = CmsCollectionsGenericWrapper.createLRUMap(cacheSize); m_onlineJsps = Collections.synchronizedMap(map); } /** * Parses the JSP and modifies OpenCms critical directive information.<p> * * @param byteContent the original JSP content * @param encoding the encoding to use for the JSP * @param controller the controller for the JSP integration * @param updatedFiles a Set containing all JSP pages that have been already updated * @param isHardInclude indicated if this page is actually a "hard" include with <code><%@ include file="..." ></code> * * @return the modified JSP content */ protected byte[] parseJsp( byte[] byteContent, String encoding, CmsFlexController controller, Set<String> updatedFiles, boolean isHardInclude) { String content; // make sure encoding is set correctly try { content = new String(byteContent, encoding); } catch (UnsupportedEncodingException e) { // encoding property is not set correctly LOG.error( Messages.get().getBundle().key( Messages.LOG_UNSUPPORTED_ENC_1, controller.getCurrentRequest().getElementUri()), e); try { encoding = OpenCms.getSystemInfo().getDefaultEncoding(); content = new String(byteContent, encoding); } catch (UnsupportedEncodingException e2) { // should not happen since default encoding is always a valid encoding (checked during system startup) content = new String(byteContent); } } // parse for special %(link:...) macros content = parseJspLinkMacros(content, controller); // parse for special <%@cms file="..." %> tag content = parseJspCmsTag(content, controller, updatedFiles); // parse for included files in tags content = parseJspIncludes(content, controller, updatedFiles); // parse for <%@page pageEncoding="..." %> tag content = parseJspEncoding(content, encoding, isHardInclude); // Processes magic taglib attributes in page directives content = processTaglibAttributes(content); // convert the result to bytes and return it try { return content.getBytes(encoding); } catch (UnsupportedEncodingException e) { // should not happen since encoding was already checked return content.getBytes(); } } /** * Parses the JSP content for the special <code><%cms file="..." %></code> tag.<p> * * @param content the JSP content to parse * @param controller the current JSP controller * @param updatedFiles a set of already updated jsp files * * @return the parsed JSP content */ protected String parseJspCmsTag(String content, CmsFlexController controller, Set<String> updatedFiles) { // check if a JSP directive occurs in the file int i1 = content.indexOf(DIRECTIVE_START); if (i1 < 0) { // no directive occurs return content; } StringBuffer buf = new StringBuffer(content.length()); int p0 = 0, i2 = 0, slen = DIRECTIVE_START.length(), elen = DIRECTIVE_END.length(); while (i1 >= 0) { // parse the file and replace JSP filename references i2 = content.indexOf(DIRECTIVE_END, i1 + slen); if (i2 < 0) { // wrong syntax (missing end directive) - let the JSP compiler produce the error message return content; } else if (i2 > i1) { String directive = content.substring(i1 + slen, i2); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_DIRECTIVE_DETECTED_3, DIRECTIVE_START, directive, DIRECTIVE_END)); } int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; while (directive.charAt(t1) == ' ') { t1++; } String argument = null; if (directive.startsWith("cms", t1)) { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "cms")); } t2 = directive.indexOf("file", t1 + 3); t5 = 4; } if (t2 > 0) { String sub = directive.substring(t2 + t5); char c1 = sub.charAt(t3); while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) { c1 = sub.charAt(++t3); } t4 = t3; while (c1 != '"') { c1 = sub.charAt(++t4); } if (t4 > t3) { argument = sub.substring(t3, t4); } if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_DIRECTIVE_ARG_1, argument)); } } if (argument != null) { // try to update the referenced file String jspname = updateJsp(argument, controller, updatedFiles); if (jspname != null) { directive = jspname; if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_DIRECTIVE_CHANGED_3, DIRECTIVE_START, directive, DIRECTIVE_END)); } } // cms directive was found buf.append(content.substring(p0, i1)); buf.append(directive); p0 = i2 + elen; i1 = content.indexOf(DIRECTIVE_START, p0); } else { // cms directive was not found buf.append(content.substring(p0, i1 + slen)); buf.append(directive); p0 = i2; i1 = content.indexOf(DIRECTIVE_START, p0); } } } if (i2 > 0) { // the content of the JSP was changed buf.append(content.substring(p0, content.length())); content = buf.toString(); } return content; } /** * Parses the JSP content for the <code><%page pageEncoding="..." %></code> tag * and ensures that the JSP page encoding is set according to the OpenCms * "content-encoding" property value of the JSP.<p> * * @param content the JSP content to parse * @param encoding the encoding to use for the JSP * @param isHardInclude indicated if this page is actually a "hard" include with <code><%@ include file="..." ></code> * * @return the parsed JSP content */ protected String parseJspEncoding(String content, String encoding, boolean isHardInclude) { // check if a JSP directive occurs in the file int i1 = content.indexOf(DIRECTIVE_START); if (i1 < 0) { // no directive occurs if (isHardInclude) { return content; } } StringBuffer buf = new StringBuffer(content.length() + 64); int p0 = 0, i2 = 0, slen = DIRECTIVE_START.length(); boolean found = false; if (i1 < 0) { // no directive found at all, append content to buffer buf.append(content); } while (i1 >= 0) { // parse the file and set/replace page encoding i2 = content.indexOf(DIRECTIVE_END, i1 + slen); if (i2 < 0) { // wrong syntax (missing end directive) - let the JSP compiler produce the error message return content; } else if (i2 > i1) { String directive = content.substring(i1 + slen, i2); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_DIRECTIVE_DETECTED_3, DIRECTIVE_START, directive, DIRECTIVE_END)); } int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; while (directive.charAt(t1) == ' ') { t1++; } String argument = null; if (directive.startsWith("page", t1)) { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "page")); } t2 = directive.indexOf("pageEncoding", t1 + 4); t5 = 12; if (t2 > 0) { found = true; } } if (t2 > 0) { String sub = directive.substring(t2 + t5); char c1 = sub.charAt(t3); while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) { c1 = sub.charAt(++t3); } t4 = t3; while (c1 != '"') { c1 = sub.charAt(++t4); } if (t4 > t3) { argument = sub.substring(t3, t4); } if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_DIRECTIVE_ARG_1, argument)); } } if (argument != null) { // a pageEncoding setting was found, changes have to be made String pre = directive.substring(0, t2 + t3 + t5); String suf = directive.substring(t2 + t3 + t5 + argument.length()); // change the encoding directive = pre + encoding + suf; if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_DIRECTIVE_CHANGED_3, DIRECTIVE_START, directive, DIRECTIVE_END)); } } buf.append(content.substring(p0, i1 + slen)); buf.append(directive); p0 = i2; i1 = content.indexOf(DIRECTIVE_START, p0); } } if (i2 > 0) { // the content of the JSP was changed buf.append(content.substring(p0, content.length())); } if (found) { content = buf.toString(); } else if (!isHardInclude) { // encoding setting was not found // if this is not a "hard" include then add the encoding to the top of the page // checking for the hard include is important to prevent errors with // multiple page encoding settings if a template is composed from several hard included elements // this is an issue in Tomcat 4.x but not 5.x StringBuffer buf2 = new StringBuffer(buf.length() + 32); buf2.append("<%@ page pageEncoding=\""); buf2.append(encoding); buf2.append("\" %>"); buf2.append(buf); content = buf2.toString(); } return content; } /** * Parses the JSP content for includes and replaces all OpenCms VFS * path information with information for the real FS.<p> * * @param content the JSP content to parse * @param controller the current JSP controller * @param updatedFiles a set of already updated files * * @return the parsed JSP content */ protected String parseJspIncludes(String content, CmsFlexController controller, Set<String> updatedFiles) { // check if a JSP directive occurs in the file int i1 = content.indexOf(DIRECTIVE_START); if (i1 < 0) { // no directive occurs return content; } StringBuffer buf = new StringBuffer(content.length()); int p0 = 0, i2 = 0, slen = DIRECTIVE_START.length(); while (i1 >= 0) { // parse the file and replace JSP filename references i2 = content.indexOf(DIRECTIVE_END, i1 + slen); if (i2 < 0) { // wrong syntax (missing end directive) - let the JSP compiler produce the error message return content; } else if (i2 > i1) { String directive = content.substring(i1 + slen, i2); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_DIRECTIVE_DETECTED_3, DIRECTIVE_START, directive, DIRECTIVE_END)); } int t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0; while (directive.charAt(t1) == ' ') { t1++; } String argument = null; if (directive.startsWith("include", t1)) { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "include")); } t2 = directive.indexOf("file", t1 + 7); t5 = 6; } else if (directive.startsWith("page", t1)) { if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_X_DIRECTIVE_DETECTED_1, "page")); } t2 = directive.indexOf("errorPage", t1 + 4); t5 = 11; } if (t2 > 0) { String sub = directive.substring(t2 + t5); char c1 = sub.charAt(t3); while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) { c1 = sub.charAt(++t3); } t4 = t3; while (c1 != '"') { c1 = sub.charAt(++t4); } if (t4 > t3) { argument = sub.substring(t3, t4); } if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_DIRECTIVE_ARG_1, argument)); } } if (argument != null) { // a file was found, changes have to be made String pre = directive.substring(0, t2 + t3 + t5); String suf = directive.substring(t2 + t3 + t5 + argument.length()); // now try to update the referenced file String jspname = updateJsp(argument, controller, updatedFiles); if (jspname != null) { // only change something in case no error had occurred directive = pre + jspname + suf; if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key( Messages.LOG_DIRECTIVE_CHANGED_3, DIRECTIVE_START, directive, DIRECTIVE_END)); } } } buf.append(content.substring(p0, i1 + slen)); buf.append(directive); p0 = i2; i1 = content.indexOf(DIRECTIVE_START, p0); } } if (i2 > 0) { // the content of the JSP was changed buf.append(content.substring(p0, content.length())); content = buf.toString(); } return content; } /** * Parses all jsp link macros, and replace them by the right target path.<p> * * @param content the content to parse * @param controller the request controller * * @return the parsed content */ protected String parseJspLinkMacros(String content, CmsFlexController controller) { CmsJspLinkMacroResolver macroResolver = new CmsJspLinkMacroResolver(controller.getCmsObject(), null, true); return macroResolver.resolveMacros(content); } /** * Returns the jsp resource identified by the given name, using the controllers cms context.<p> * * @param controller the flex controller * @param jspName the name of the jsp * * @return an OpenCms resource * * @throws CmsException if something goes wrong */ protected CmsResource readJspResource(CmsFlexController controller, String jspName) throws CmsException { // create an OpenCms user context that operates in the root site CmsObject cms = OpenCms.initCmsObject(controller.getCmsObject()); // we only need to change the site, but not the project, // since the request has already the right project set cms.getRequestContext().setSiteRoot(""); // try to read the resource return cms.readResource(jspName); } /** * Delivers the plain uninterpreted resource with escaped XML.<p> * * This is intended for viewing historical versions.<p> * * @param cms the initialized CmsObject which provides user permissions * @param file the requested OpenCms VFS resource * @param req the servlet request * @param res the servlet response * * @throws IOException might be thrown by the servlet environment * @throws CmsException in case of errors accessing OpenCms functions */ protected void showSource(CmsObject cms, CmsResource file, HttpServletRequest req, HttpServletResponse res) throws CmsException, IOException { CmsResource historyResource = (CmsResource)CmsHistoryResourceHandler.getHistoryResource(req); if (historyResource == null) { historyResource = file; } CmsFile historyFile = cms.readFile(historyResource); String content = new String(historyFile.getContents()); // change the content-type header so that browsers show plain text res.setContentLength(content.length()); res.setContentType("text/plain"); Writer out = res.getWriter(); out.write(content); } /** * Updates a JSP page in the "real" file system in case the VFS resource has changed based on the resource name.<p> * * Generates a resource based on the provided name and calls {@link #updateJsp(CmsResource, CmsFlexController, Set)}.<p> * * @param vfsName the name of the JSP file resource in the VFS * @param controller the controller for the JSP integration * @param updatedFiles a Set containing all JSP pages that have been already updated * * @return the file name of the updated JSP in the "real" FS */ protected String updateJsp(String vfsName, CmsFlexController controller, Set<String> updatedFiles) { String jspVfsName = CmsLinkManager.getAbsoluteUri(vfsName, controller.getCurrentRequest().getElementRootPath()); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_UPDATE_JSP_1, jspVfsName)); } String jspRfsName; try { CmsResource includeResource; try { // first try a root path includeResource = readJspResource(controller, jspVfsName); } catch (CmsVfsResourceNotFoundException e) { // if fails, try a site relative path includeResource = readJspResource( controller, controller.getCmsObject().getRequestContext().addSiteRoot(jspVfsName)); } // make sure the jsp referenced file is generated jspRfsName = updateJsp(includeResource, controller, updatedFiles); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_NAME_REAL_FS_1, jspRfsName)); } } catch (Exception e) { jspRfsName = null; if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_UPDATE_1, jspVfsName), e); } } return jspRfsName; } /** * Updates all jsp files that include the given jsp file using the 'link.strong' macro.<p> * * @param resource the current updated jsp file * @param controller the controller for the jsp integration * @param updatedFiles the already updated files * * @return <code>true</code> if the given JSP file should be updated due to dirty included files * * @throws ServletException might be thrown in the process of including the JSP * @throws IOException might be thrown in the process of including the JSP * @throws CmsLoaderException if the resource type can not be read */ protected boolean updateStrongLinks(CmsResource resource, CmsFlexController controller, Set<String> updatedFiles) throws CmsLoaderException, IOException, ServletException { int numberOfUpdates = updatedFiles.size(); CmsObject cms = controller.getCmsObject(); CmsRelationFilter filter = CmsRelationFilter.TARGETS.filterType(CmsRelationType.JSP_STRONG); Iterator<CmsRelation> it; try { it = cms.getRelationsForResource(resource, filter).iterator(); } catch (CmsException e) { // should never happen if (LOG.isErrorEnabled()) { LOG.error(e.getLocalizedMessage(), e); } return false; } while (it.hasNext()) { CmsRelation relation = it.next(); CmsResource target = null; try { target = relation.getTarget(cms, CmsResourceFilter.DEFAULT); } catch (CmsException e) { // should never happen if (LOG.isErrorEnabled()) { LOG.error(e.getLocalizedMessage(), e); } continue; } // prevent recursive update when including the same file if (resource.equals(target)) { continue; } // update the target updateJsp(target, controller, updatedFiles); } // the current jsp file should be updated only if one of the included jsp has been updated return numberOfUpdates < updatedFiles.size(); } /** * Returns the read-write-lock for the given jsp vfs name.<p> * * @param jspVfsName the jsp vfs name * * @return the read-write-lock */ private ReentrantReadWriteLock getFileLock(String jspVfsName) { synchronized (m_fileLocks) { if (!m_fileLocks.containsKey(jspVfsName)) { m_fileLocks.put(jspVfsName, new ReentrantReadWriteLock(true)); } return m_fileLocks.get(jspVfsName); } } }