All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.xml.CmsXmlEntityResolver Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 18.0
Show newest version
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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.xml;

import org.opencms.configuration.CmsConfigurationManager;
import org.opencms.db.CmsDriverManager;
import org.opencms.db.CmsPublishedResource;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
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.monitor.CmsMemoryMonitor;
import org.opencms.util.CmsCollectionsGenericWrapper;
import org.opencms.util.CmsFileUtil;
import org.opencms.util.CmsUUID;
import org.opencms.xml.page.CmsXmlPage;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

/**
 * Resolves XML entities (e.g. external DTDs) in the OpenCms VFS.

* * Also provides a cache for XML content schema definitions.

* * @since 6.0.0 */ public class CmsXmlEntityResolver implements EntityResolver, I_CmsEventListener { /** Maximum size of the content definition cache. */ public static final int CONTENT_DEFINITION_CACHE_SIZE = 2048; /** Scheme for files which should be retrieved from the classpath. */ public static final String INTERNAL_SCHEME = "internal://"; /** The scheme to identify a file in the OpenCms VFS. */ public static final String OPENCMS_SCHEME = "opencms://"; /** * A list of string pairs used to translate legacy system ids to a new form. The first component of each pair * is the prefix which should be replaced by the second component of that pair. */ private static final String[][] LEGACY_TRANSLATIONS = { {"opencms://system/modules/org.opencms.ade.config/schemas/", "internal://org/opencms/xml/adeconfig/"}, { "opencms://system/modules/org.opencms.ade.containerpage/schemas/", "internal://org/opencms/xml/containerpage/"}, {"opencms://system/modules/org.opencms.ade.sitemap/schemas/", "internal://org/opencms/xml/adeconfig/sitemap/"}}; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsXmlEntityResolver.class); /** A temporary cache for XML content definitions. */ private static Map m_cacheContentDefinitions; /** A permanent cache to avoid multiple readings of often used files from the VFS. */ private static Map m_cachePermanent; /** A temporary cache to avoid multiple readings of often used files from the VFS. */ private static Map m_cacheTemporary; /** The location of the XML page XML schema. */ private static final String XMLPAGE_OLD_DTD_LOCATION = "org/opencms/xml/page/xmlpage.dtd"; /** The (old) DTD address of the OpenCms xmlpage (used in 5.3.5). */ private static final String XMLPAGE_OLD_DTD_SYSTEM_ID_1 = "http://www.opencms.org/dtd/6.0/xmlpage.dtd"; /** The (old) DTD address of the OpenCms xmlpage (used until 5.3.5). */ private static final String XMLPAGE_OLD_DTD_SYSTEM_ID_2 = "/system/shared/page.dtd"; /** The location of the xmlpage XSD. */ private static final String XMLPAGE_XSD_LOCATION = "org/opencms/xml/page/xmlpage.xsd"; /** The cms object to use for VFS access (will be initialized with "Guest" permissions). */ private CmsObject m_cms; /** * Creates a new XML entity resolver based on the provided CmsObject.

* * If the provided CmsObject is null, then the OpenCms VFS is not * searched for XML entities, however the internal cache and * other OpenCms internal entities not in the VFS are still resolved.

* * @param cms the cms context to use for resolving XML files from the OpenCms VFS */ public CmsXmlEntityResolver(CmsObject cms) { initCaches(); m_cms = cms; } /** * Adds a system ID URL to to internal permanent cache.

* * This cache will NOT be cleared automatically.

* * @param systemId the system ID to add * @param content the content of the system id */ public static void cacheSystemId(String systemId, byte[] content) { initCaches(); m_cachePermanent.put(systemId, content); } /** * Checks if a given system ID URL is in the internal permanent cache.

* * This check is required to see if a XML content is based on a file that actually exists in the OpenCms VFS, * or if the schema has been just cached without a VFS file.

* * @param systemId the system id ID check * * @return true if the system ID is in the internal permanent cache, false otherwise */ public static boolean isCachedSystemId(String systemId) { if (m_cachePermanent != null) { return m_cachePermanent.containsKey(systemId); } return false; } /** * Checks whether the given schema id is an internal schema id or is translated to an internal schema id.

* @param schema the schema id * @return true if the given schema id is an internal schema id or translated to an internal schema id */ public static boolean isInternalId(String schema) { String translatedId = translateLegacySystemId(schema); if (translatedId.startsWith(INTERNAL_SCHEME)) { return true; } return false; } /** * Initialize the OpenCms XML entity resolver.

* * @param adminCms an initialized OpenCms user context with "Administrator" role permissions * @param typeSchemaBytes the base widget type XML schema definitions * * @see CmsXmlContentTypeManager#initialize(CmsObject) */ protected static void initialize(CmsObject adminCms, byte[] typeSchemaBytes) { // create the resolver to register as event listener CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(adminCms); // register this object as event listener OpenCms.addCmsEventListener( resolver, new int[] { I_CmsEventListener.EVENT_CLEAR_CACHES, I_CmsEventListener.EVENT_PUBLISH_PROJECT, I_CmsEventListener.EVENT_RESOURCE_MODIFIED, I_CmsEventListener.EVENT_RESOURCE_MOVED, I_CmsEventListener.EVENT_RESOURCE_DELETED}); // cache the base widget type XML schema definitions cacheSystemId(CmsXmlContentDefinition.XSD_INCLUDE_OPENCMS, typeSchemaBytes); } /** * Initializes the internal caches for permanent and temporary system IDs.

*/ private static void initCaches() { if (m_cacheTemporary == null) { m_cacheTemporary = CmsMemoryMonitor.createLRUCacheMap(1024); m_cachePermanent = new ConcurrentHashMap(32); m_cacheContentDefinitions = CmsMemoryMonitor.createLRUCacheMap(CONTENT_DEFINITION_CACHE_SIZE); } if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) { if ((OpenCms.getMemoryMonitor() != null) && !OpenCms.getMemoryMonitor().isMonitoring(CmsXmlEntityResolver.class.getName() + ".cacheTemporary")) { // reinitialize the caches after the memory monitor is set up Map cacheTemporary = CmsMemoryMonitor.createLRUCacheMap(128); cacheTemporary.putAll(m_cacheTemporary); m_cacheTemporary = cacheTemporary; OpenCms.getMemoryMonitor().register( CmsXmlEntityResolver.class.getName() + ".cacheTemporary", cacheTemporary); Map cachePermanent = new ConcurrentHashMap(32); cachePermanent.putAll(m_cachePermanent); m_cachePermanent = cachePermanent; OpenCms.getMemoryMonitor().register( CmsXmlEntityResolver.class.getName() + ".cachePermanent", cachePermanent); Map cacheContentDefinitions = CmsMemoryMonitor.createLRUCacheMap( CONTENT_DEFINITION_CACHE_SIZE); cacheContentDefinitions.putAll(m_cacheContentDefinitions); m_cacheContentDefinitions = cacheContentDefinitions; OpenCms.getMemoryMonitor().register( CmsXmlEntityResolver.class.getName() + ".cacheContentDefinitions", cacheContentDefinitions); } } } /** * Translates a legacy system id to a new form.

* * @param systemId the original system id * @return the new system id */ private static String translateLegacySystemId(String systemId) { String result = systemId; for (String[] translation : LEGACY_TRANSLATIONS) { if (systemId.startsWith(translation[0])) { // replace prefix with second component if it matches the first component result = translation[1] + systemId.substring(translation[0].length()); break; } } if (OpenCms.getRepositoryManager() != null) { result = OpenCms.getResourceManager().getXsdTranslator().translateResource(result); } return result; } /** * Caches an XML content definition based on the given system id and the online / offline status * of this entity resolver instance.

* * @param systemId the system id to use as cache key * @param contentDefinition the content definition to cache */ public void cacheContentDefinition(String systemId, CmsXmlContentDefinition contentDefinition) { String cacheKey = getCacheKeyForCurrentProject(systemId); m_cacheContentDefinitions.put(cacheKey, contentDefinition); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_CACHED_SYSTEM_ID_1, cacheKey)); } } /** * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent) */ public void cmsEvent(CmsEvent event) { CmsResource resource; switch (event.getType()) { case I_CmsEventListener.EVENT_PUBLISH_PROJECT: // only flush cache if a schema definition where published CmsUUID publishHistoryId = new CmsUUID((String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID)); if (isSchemaDefinitionInPublishList(publishHistoryId)) { m_cacheTemporary.clear(); m_cacheContentDefinitions.clear(); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_FLUSHED_CACHES_0)); } } break; case I_CmsEventListener.EVENT_CLEAR_CACHES: // flush cache m_cacheTemporary.clear(); m_cacheContentDefinitions.clear(); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_FLUSHED_CACHES_0)); } break; case I_CmsEventListener.EVENT_RESOURCE_MODIFIED: Object change = event.getData().get(I_CmsEventListener.KEY_CHANGE); if ((change != null) && change.equals(new Integer(CmsDriverManager.NOTHING_CHANGED))) { // skip lock & unlock return; } resource = (CmsResource)event.getData().get(I_CmsEventListener.KEY_RESOURCE); uncacheSystemId(resource.getRootPath()); break; case I_CmsEventListener.EVENT_RESOURCE_DELETED: case I_CmsEventListener.EVENT_RESOURCE_MOVED: List resources = CmsCollectionsGenericWrapper.list( event.getData().get(I_CmsEventListener.KEY_RESOURCES)); for (int i = 0; i < resources.size(); i++) { resource = resources.get(i); uncacheSystemId(resource.getRootPath()); } break; default: // no operation } } /** * Looks up the given XML content definition system id in the internal content definition cache.

* * @param systemId the system id of the XML content definition to look up * * @return the XML content definition found, or null if no definition is cached for the given system id */ public CmsXmlContentDefinition getCachedContentDefinition(String systemId) { String cacheKey = getCacheKeyForCurrentProject(systemId); CmsXmlContentDefinition result = m_cacheContentDefinitions.get(cacheKey); if ((result != null) && LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_CACHE_LOOKUP_SUCCEEDED_1, cacheKey)); } return result; } /** * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String) */ public InputSource resolveEntity(String publicId, String systemId) { // lookup the system id caches first byte[] content; systemId = translateLegacySystemId(systemId); content = m_cachePermanent.get(systemId); if (content != null) { // permanent cache contains system id return createInputSource(content, systemId); } else if (systemId.equals(CmsXmlPage.XMLPAGE_XSD_SYSTEM_ID)) { // XML page XSD reference try { InputStream stream = getClass().getClassLoader().getResourceAsStream(XMLPAGE_XSD_LOCATION); content = CmsFileUtil.readFully(stream); // cache the XML page DTD m_cachePermanent.put(systemId, content); return createInputSource(content, systemId); } catch (Throwable t) { LOG.error( Messages.get().getBundle().key(Messages.LOG_XMLPAGE_XSD_NOT_FOUND_1, XMLPAGE_XSD_LOCATION), t); } } else if (systemId.equals(XMLPAGE_OLD_DTD_SYSTEM_ID_1) || systemId.endsWith(XMLPAGE_OLD_DTD_SYSTEM_ID_2)) { // XML page DTD reference try { InputStream stream = getClass().getClassLoader().getResourceAsStream(XMLPAGE_OLD_DTD_LOCATION); // cache the XML page DTD content = CmsFileUtil.readFully(stream); m_cachePermanent.put(systemId, content); return createInputSource(content, systemId); } catch (Throwable t) { LOG.error( Messages.get().getBundle().key(Messages.LOG_XMLPAGE_DTD_NOT_FOUND_1, XMLPAGE_OLD_DTD_LOCATION), t); } } else if ((m_cms != null) && systemId.startsWith(OPENCMS_SCHEME)) { // opencms:// VFS reference String cacheSystemId = systemId.substring(OPENCMS_SCHEME.length() - 1); String cacheKey = getCacheKey( cacheSystemId, m_cms.getRequestContext().getCurrentProject().isOnlineProject()); // look up temporary cache content = m_cacheTemporary.get(cacheKey); if (content != null) { return createInputSource(content, systemId); } String storedSiteRoot = m_cms.getRequestContext().getSiteRoot(); try { // content not cached, read from VFS m_cms.getRequestContext().setSiteRoot("/"); CmsFile file = m_cms.readFile(cacheSystemId, CmsResourceFilter.IGNORE_EXPIRATION); content = file.getContents(); // store content in cache m_cacheTemporary.put(cacheKey, content); if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_ERR_CACHED_SYS_ID_1, cacheKey)); } return createInputSource(content, systemId); } catch (Throwable t) { LOG.error(Messages.get().getBundle().key(Messages.LOG_ENTITY_RESOLVE_FAILED_1, systemId), t); } finally { m_cms.getRequestContext().setSiteRoot(storedSiteRoot); } } else if (systemId.startsWith(INTERNAL_SCHEME)) { String location = systemId.substring(INTERNAL_SCHEME.length()); try { InputStream stream = getClass().getClassLoader().getResourceAsStream(location); content = CmsFileUtil.readFully(stream); m_cachePermanent.put(systemId, content); return createInputSource(content, systemId); } catch (Throwable t) { LOG.error(t.getLocalizedMessage(), t); } } else if (systemId.substring(0, systemId.lastIndexOf("/") + 1).equalsIgnoreCase( CmsConfigurationManager.DEFAULT_DTD_PREFIX)) { // default DTD location in the org.opencms.configuration package String location = null; try { String dtdFilename = systemId.substring(systemId.lastIndexOf("/") + 1); location = CmsConfigurationManager.DEFAULT_DTD_LOCATION + dtdFilename; InputStream stream = getClass().getClassLoader().getResourceAsStream(location); content = CmsFileUtil.readFully(stream); // cache the DTD m_cachePermanent.put(systemId, content); return createInputSource(content, systemId); } catch (Throwable t) { LOG.error(Messages.get().getBundle().key(Messages.LOG_DTD_NOT_FOUND_1, location), t); } } // use the default behaviour (i.e. resolve through external URL) return null; } /** * Removes a cached entry for a system id (filename) from the internal offline temporary and content definition caches.

* * The online resources cached for the online project are only flushed when a project is published.

* * @param systemId the system id (filename) to remove from the cache */ public void uncacheSystemId(String systemId) { Object o; o = m_cacheTemporary.remove(getCacheKey(systemId, false)); if (null != o) { // if an object was removed from the temporary cache, all XML content definitions must be cleared // because this may be a nested subschema m_cacheContentDefinitions.clear(); if (LOG.isDebugEnabled()) { LOG.debug( Messages.get().getBundle().key(Messages.LOG_ERR_UNCACHED_SYS_ID_1, getCacheKey(systemId, false))); } } else { // check if a cached content definition has to be removed based on the system id o = m_cacheContentDefinitions.remove(getCacheKey(systemId, false)); if ((null != o) && LOG.isDebugEnabled()) { LOG.debug( Messages.get().getBundle().key( Messages.LOG_ERR_UNCACHED_CONTENT_DEF_1, getCacheKey(systemId, false))); } } } /** * Creates an input source for the given byte data and system id.

* * @param data the data which the input source should return * @param systemId the system id for the input source * * @return the input source */ InputSource createInputSource(byte[] data, String systemId) { InputSource result = new InputSource(new ByteArrayInputStream(data)); result.setSystemId(systemId); return result; } /** * Returns a cache key for the given system id (filename) based on the status * of the given project flag.

* * @param systemId the system id (filename) to get the cache key for * @param online indicates if this key is generated for the online project * * @return the cache key for the system id */ private String getCacheKey(String systemId, boolean online) { if (online) { return "online_".concat(systemId); } return "offline_".concat(systemId); } /** * Returns a cache key for the given system id (filename) based on the status * of the internal CmsObject.

* * @param systemId the system id (filename) to get the cache key for * * @return the cache key for the system id */ private String getCacheKeyForCurrentProject(String systemId) { // check the project boolean project = (m_cms != null) ? m_cms.getRequestContext().getCurrentProject().isOnlineProject() : false; // remove opencms:// prefix if (systemId.startsWith(OPENCMS_SCHEME)) { systemId = systemId.substring(OPENCMS_SCHEME.length() - 1); } return getCacheKey(systemId, project); } /** * Proves if there is at least one xsd or dtd file in the list of resources to publish.

* * @param publishHistoryId the publish history id * * @return true, if there is at least one xsd or dtd file in the list of resources to publish, otherwise false */ private boolean isSchemaDefinitionInPublishList(CmsUUID publishHistoryId) { if (m_cms == null) { // CmsObject not available, assume there may be a schema definition in the publish history return true; } try { List publishedResources = m_cms.readPublishedResources(publishHistoryId); for (CmsPublishedResource cmsPublishedResource : publishedResources) { String resourceRootPath = cmsPublishedResource.getRootPath(); String resourceRootPathLowerCase = resourceRootPath.toLowerCase(); if (resourceRootPathLowerCase.endsWith(".xsd") || resourceRootPathLowerCase.endsWith(".dtd") || m_cacheTemporary.containsKey(getCacheKey(resourceRootPath, true))) { return true; } } } catch (CmsException e) { // error reading published Resources. LOG.warn(e.getMessage(), e); } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy