KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opencms > xml > CmsXmlEntityResolver


1 /*
2  * File : $Source: /usr/local/cvs/opencms/src/org/opencms/xml/CmsXmlEntityResolver.java,v $
3  * Date : $Date: 2006/04/28 15:20:52 $
4  * Version: $Revision: 1.25 $
5  *
6  * This library is part of OpenCms -
7  * the Open Source Content Mananagement System
8  *
9  * Copyright (c) 2005 Alkacon Software GmbH (http://www.alkacon.com)
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * For further information about Alkacon Software GmbH, please see the
22  * company website: http://www.alkacon.com
23  *
24  * For further information about OpenCms, please see the
25  * project website: http://www.opencms.org
26  *
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with this library; if not, write to the Free Software
29  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30  */

31
32 package org.opencms.xml;
33
34 import org.opencms.configuration.CmsConfigurationManager;
35 import org.opencms.file.CmsFile;
36 import org.opencms.file.CmsObject;
37 import org.opencms.file.CmsResource;
38 import org.opencms.main.CmsEvent;
39 import org.opencms.main.CmsLog;
40 import org.opencms.main.I_CmsEventListener;
41 import org.opencms.main.OpenCms;
42 import org.opencms.util.CmsFileUtil;
43 import org.opencms.xml.page.CmsXmlPage;
44
45 import java.io.ByteArrayInputStream JavaDoc;
46 import java.io.InputStream JavaDoc;
47 import java.util.Collections JavaDoc;
48 import java.util.HashMap JavaDoc;
49 import java.util.List JavaDoc;
50 import java.util.Map JavaDoc;
51
52 import org.apache.commons.collections.map.LRUMap;
53 import org.apache.commons.logging.Log;
54
55 import org.xml.sax.EntityResolver JavaDoc;
56 import org.xml.sax.InputSource JavaDoc;
57
58 /**
59  * Resolves XML entities (e.g. external DTDs) in the OpenCms VFS.<p>
60  *
61  * Also provides a cache for XML content schema definitions.<p>
62  *
63  * @author Alexander Kandzior
64  *
65  * @version $Revision: 1.25 $
66  *
67  * @since 6.0.0
68  */

69 public class CmsXmlEntityResolver implements EntityResolver JavaDoc, I_CmsEventListener {
70
71     /** The scheme to identify a file in the OpenCms VFS. */
72     public static final String JavaDoc OPENCMS_SCHEME = "opencms://";
73
74     /** The log object for this class. */
75     private static final Log LOG = CmsLog.getLog(CmsXmlEntityResolver.class);
76
77     /** A temporary cache for XML content definitions. */
78     private static Map JavaDoc m_cacheContentDefinitions;
79
80     /** A permanent cache to avoid multiple readings of often used files from the VFS. */
81     private static Map JavaDoc m_cachePermanent;
82
83     /** A temporary cache to avoid multiple readings of often used files from the VFS. */
84     private static Map JavaDoc m_cacheTemporary;
85
86     /** The location of the XML page XML schema. */
87     private static final String JavaDoc XMLPAGE_OLD_DTD_LOCATION = "org/opencms/xml/page/xmlpage.dtd";
88
89     /** The (old) DTD address of the OpenCms xmlpage (used in 5.3.5). */
90     private static final String JavaDoc XMLPAGE_OLD_DTD_SYSTEM_ID_1 = "http://www.opencms.org/dtd/6.0/xmlpage.dtd";
91
92     /** The (old) DTD address of the OpenCms xmlpage (used until 5.3.5). */
93     private static final String JavaDoc XMLPAGE_OLD_DTD_SYSTEM_ID_2 = "/system/shared/page.dtd";
94
95     /** The location of the xmlpage XSD. */
96     private static final String JavaDoc XMLPAGE_XSD_LOCATION = "org/opencms/xml/page/xmlpage.xsd";
97
98     /** The cms object to use for VFS access (will be initialized with "Guest" permissions). */
99     private CmsObject m_cms;
100
101     /**
102      * Creates a new XML entity resolver based on the provided CmsObject.<p>
103      *
104      * If the provided CmsObject is null, then the OpenCms VFS is not
105      * searched for XML entities, however the internal cache and
106      * other OpenCms internal entities not in the VFS are still resolved.<p>
107      *
108      * @param cms the cms context to use for resolving XML files from the OpenCms VFS
109      */

110     public CmsXmlEntityResolver(CmsObject cms) {
111
112         initCaches();
113         m_cms = cms;
114     }
115
116     /**
117      * Adds a sytem id URL to to internal permanent cache.<p>
118      *
119      * This cache will NOT be cleared automatically.<p>
120      *
121      * @param systemId the system id to add
122      * @param content the content of the system id
123      */

124     public static void cacheSystemId(String JavaDoc systemId, byte[] content) {
125
126         initCaches();
127         m_cachePermanent.put(systemId, content);
128     }
129
130     /**
131      * Initialize the OpenCms XML entity resolver.<p>
132      *
133      * @param adminCms an initialized OpenCms user context with "Administrator" role permissions
134      * @param typeSchemaBytes the base widget type XML schema definitions
135      *
136      * @see CmsXmlContentTypeManager#initialize(CmsObject)
137      */

138     protected static void initialize(CmsObject adminCms, byte[] typeSchemaBytes) {
139
140         // create the resolver to register as event listener
141
CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(adminCms);
142
143         // register this object as event listener
144
OpenCms.addCmsEventListener(resolver, new int[] {
145             I_CmsEventListener.EVENT_CLEAR_CACHES,
146             I_CmsEventListener.EVENT_PUBLISH_PROJECT,
147             I_CmsEventListener.EVENT_RESOURCE_MODIFIED,
148             I_CmsEventListener.EVENT_RESOURCE_DELETED});
149
150         // cache the base widget type XML schema definitions
151
cacheSystemId(CmsXmlContentDefinition.XSD_INCLUDE_OPENCMS, typeSchemaBytes);
152     }
153
154     /**
155      * Initializes the internal caches for permanent and temporary system IDs.<p>
156      */

157     private static void initCaches() {
158
159         if (m_cacheTemporary == null) {
160
161             LRUMap cacheTemporary = new LRUMap(128);
162             m_cacheTemporary = Collections.synchronizedMap(cacheTemporary);
163
164             HashMap JavaDoc cachePermanent = new HashMap JavaDoc(32);
165             m_cachePermanent = Collections.synchronizedMap(cachePermanent);
166
167             LRUMap cacheContentDefinitions = new LRUMap(64);
168             m_cacheContentDefinitions = Collections.synchronizedMap(cacheContentDefinitions);
169
170             if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
171                 if ((OpenCms.getMemoryMonitor() != null) && OpenCms.getMemoryMonitor().enabled()) {
172                     // map must be of type "LRUMap" so that memory monitor can acecss all information
173
OpenCms.getMemoryMonitor().register(
174                         CmsXmlEntityResolver.class.getName() + ".m_cacheTemporary",
175                         cacheTemporary);
176                     // map must be of type "HashMap" so that memory monitor can acecss all information
177
OpenCms.getMemoryMonitor().register(
178                         CmsXmlEntityResolver.class.getName() + ".m_cachePermanent",
179                         cachePermanent);
180                     // map must be of type "LRUMap" so that memory monitor can acecss all information
181
OpenCms.getMemoryMonitor().register(
182                         CmsXmlEntityResolver.class.getName() + ".m_cacheContentDefinitions",
183                         cacheContentDefinitions);
184                 }
185             }
186         }
187     }
188
189     /**
190      * Caches an XML content defintion based on the given system id and the online / offline status
191      * of this entity resolver instance.<p>
192      *
193      * @param systemId the system id to use as cache key
194      * @param contentDefinition the content definition to cache
195      */

196     public void cacheContentDefinition(String JavaDoc systemId, CmsXmlContentDefinition contentDefinition) {
197
198         String JavaDoc cacheKey = getCacheKeyForCurrentProject(systemId);
199         m_cacheContentDefinitions.put(cacheKey, contentDefinition);
200         if (LOG.isDebugEnabled()) {
201             LOG.debug(Messages.get().getBundle().key(Messages.LOG_ER_CACHED_SYSTEM_ID_1, cacheKey));
202         }
203     }
204
205     /**
206      * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent)
207      */

208     public void cmsEvent(CmsEvent event) {
209
210         CmsResource resource;
211         switch (event.getType()) {
212             case I_CmsEventListener.EVENT_PUBLISH_PROJECT:
213             case I_CmsEventListener.EVENT_CLEAR_CACHES:
214                 // flush cache
215
m_cacheTemporary.clear();
216                 m_cacheContentDefinitions.clear();
217                 if (LOG.isDebugEnabled()) {
218                     LOG.debug(Messages.get().getBundle().key(Messages.LOG_ER_FLUSHED_CACHES_0));
219                 }
220                 break;
221             case I_CmsEventListener.EVENT_RESOURCE_MODIFIED:
222                 resource = (CmsResource)event.getData().get("resource");
223                 uncacheSystemId(resource.getRootPath());
224                 break;
225             case I_CmsEventListener.EVENT_RESOURCE_DELETED:
226                 List JavaDoc resources = (List JavaDoc)event.getData().get("resources");
227                 for (int i = 0; i < resources.size(); i++) {
228                     resource = (CmsResource)resources.get(i);
229                     uncacheSystemId(resource.getRootPath());
230                 }
231                 break;
232             default:
233         // no operation
234
}
235     }
236
237     /**
238      * Looks up the given XML content definition system id in the internal content definition cache.<p>
239      *
240      * @param systemId the system id of the XML content definition to look up
241      *
242      * @return the XML content definition found, or null if no definition is cached for the given system id
243      */

244     public CmsXmlContentDefinition getCachedContentDefinition(String JavaDoc systemId) {
245
246         String JavaDoc cacheKey = getCacheKeyForCurrentProject(systemId);
247         CmsXmlContentDefinition result = (CmsXmlContentDefinition)m_cacheContentDefinitions.get(cacheKey);
248         if ((result != null) && LOG.isDebugEnabled()) {
249             LOG.debug(Messages.get().getBundle().key(Messages.LOG_CACHE_LOOKUP_SUCCEEDED_1, cacheKey));
250         }
251         return result;
252     }
253
254     /**
255      * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
256      */

257     public InputSource JavaDoc resolveEntity(String JavaDoc publicId, String JavaDoc systemId) {
258
259         // lookup the system id caches first
260
byte[] content;
261         content = (byte[])m_cachePermanent.get(systemId);
262         if (content != null) {
263
264             // permanent cache contains system id
265
return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(content));
266         } else if (systemId.equals(CmsXmlPage.XMLPAGE_XSD_SYSTEM_ID)) {
267
268             // XML page XSD reference
269
try {
270                 InputStream JavaDoc stream = getClass().getClassLoader().getResourceAsStream(XMLPAGE_XSD_LOCATION);
271                 content = CmsFileUtil.readFully(stream);
272                 // cache the XML page DTD
273
m_cachePermanent.put(systemId, content);
274                 return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(content));
275             } catch (Throwable JavaDoc t) {
276                 LOG.error(Messages.get().getBundle().key(Messages.LOG_XMLPAGE_XSD_NOT_FOUND_1, XMLPAGE_XSD_LOCATION), t);
277             }
278
279         } else if (systemId.equals(XMLPAGE_OLD_DTD_SYSTEM_ID_1) || systemId.endsWith(XMLPAGE_OLD_DTD_SYSTEM_ID_2)) {
280
281             // XML page DTD reference
282
try {
283                 InputStream JavaDoc stream = getClass().getClassLoader().getResourceAsStream(XMLPAGE_OLD_DTD_LOCATION);
284                 // cache the XML page DTD
285
content = CmsFileUtil.readFully(stream);
286                 m_cachePermanent.put(systemId, content);
287                 return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(content));
288             } catch (Throwable JavaDoc t) {
289                 LOG.error(
290                     Messages.get().getBundle().key(Messages.LOG_XMLPAGE_DTD_NOT_FOUND_1, XMLPAGE_OLD_DTD_LOCATION),
291                     t);
292             }
293         } else if ((m_cms != null) && systemId.startsWith(OPENCMS_SCHEME)) {
294
295             // opencms:// VFS reference
296
String JavaDoc cacheSystemId = systemId.substring(OPENCMS_SCHEME.length() - 1);
297             String JavaDoc cacheKey = getCacheKey(cacheSystemId, m_cms.getRequestContext().currentProject().isOnlineProject());
298             // look up temporary cache
299
content = (byte[])m_cacheTemporary.get(cacheKey);
300             if (content != null) {
301                 return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(content));
302             }
303             try {
304                 // content not cached, read from VFS
305
m_cms.getRequestContext().saveSiteRoot();
306                 m_cms.getRequestContext().setSiteRoot("/");
307                 CmsFile file = m_cms.readFile(cacheSystemId);
308                 content = file.getContents();
309                 // store content in cache
310
m_cacheTemporary.put(cacheKey, content);
311                 if (LOG.isDebugEnabled()) {
312                     LOG.debug(Messages.get().getBundle().key(Messages.LOG_ER_CACHED_SYS_ID_1, cacheKey));
313                 }
314                 return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(content));
315             } catch (Throwable JavaDoc t) {
316                 LOG.error(Messages.get().getBundle().key(Messages.LOG_ENTITY_RESOLVE_FAILED_1, systemId), t);
317             } finally {
318                 m_cms.getRequestContext().restoreSiteRoot();
319             }
320         } else if (systemId.substring(0, systemId.lastIndexOf("/") + 1).equalsIgnoreCase(
321             CmsConfigurationManager.DEFAULT_DTD_PREFIX)) {
322
323             // default DTD location in the org.opencms.configuration package
324
String JavaDoc location = null;
325             try {
326                 String JavaDoc dtdFilename = systemId.substring(systemId.lastIndexOf("/") + 1);
327                 location = CmsConfigurationManager.DEFAULT_DTD_LOCATION + dtdFilename;
328                 InputStream JavaDoc stream = getClass().getClassLoader().getResourceAsStream(location);
329                 content = CmsFileUtil.readFully(stream);
330                 // cache the DTD
331
m_cachePermanent.put(systemId, content);
332                 return new InputSource JavaDoc(new ByteArrayInputStream JavaDoc(content));
333             } catch (Throwable JavaDoc t) {
334                 LOG.error(Messages.get().getBundle().key(Messages.LOG_DTD_NOT_FOUND_1, location), t);
335             }
336
337         }
338
339         // use the default behaviour (i.e. resolve through external URL)
340
return null;
341     }
342
343     /**
344      * Returns a cache key for the given system id (filename) based on the status
345      * of the given project flag.<p>
346      *
347      * @param systemId the system id (filename) to get the cache key for
348      * @param online indicates if this key is generated for the online project
349      *
350      * @return the cache key for the system id
351      */

352     private String JavaDoc getCacheKey(String JavaDoc systemId, boolean online) {
353
354         if (online) {
355             return "online_".concat(systemId);
356         }
357         return "offline_".concat(systemId);
358     }
359
360     /**
361      * Returns a cache key for the given system id (filename) based on the status
362      * of the internal CmsObject.<p>
363      *
364      * @param systemId the system id (filename) to get the cache key for
365      *
366      * @return the cache key for the system id
367      */

368     private String JavaDoc getCacheKeyForCurrentProject(String JavaDoc systemId) {
369
370         // check the project
371
boolean project = (m_cms != null) ? m_cms.getRequestContext().currentProject().isOnlineProject() : false;
372
373         // remove opencms:// prefix
374
if (systemId.startsWith(OPENCMS_SCHEME)) {
375             systemId = systemId.substring(OPENCMS_SCHEME.length() - 1);
376         }
377
378         return getCacheKey(systemId, project);
379     }
380
381     /**
382      * Uncaches a system id (filename) from the internal offline temporary and content defintions caches.<p>
383      *
384      * The online resources cached for the online project are only flushed when a project is published.<p>
385      *
386      * @param systemId the system id (filename) to uncache
387      */

388     private void uncacheSystemId(String JavaDoc systemId) {
389
390         Object JavaDoc o;
391         o = m_cacheTemporary.remove(getCacheKey(systemId, false));
392         if (null != o) {
393             // if an object was removed from the tomporary cache, all XML content definitions must be cleared
394
// because this may be a nested subschema
395
m_cacheContentDefinitions.clear();
396             if (LOG.isDebugEnabled()) {
397                 LOG.debug(Messages.get().getBundle().key(
398                     Messages.LOG_ER_UNCACHED_SYS_ID_1,
399                     getCacheKey(systemId, false)));
400             }
401         } else {
402             // check if a cached content definition has to be removed based on the system id
403
o = m_cacheContentDefinitions.remove(getCacheKey(systemId, false));
404             if ((null != o) && LOG.isDebugEnabled()) {
405                 LOG.debug(Messages.get().getBundle().key(
406                     Messages.LOG_ER_UNCACHED_CONTENT_DEF_1,
407                     getCacheKey(systemId, false)));
408             }
409         }
410     }
411 }
Popular Tags