/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2009 Jaspersoft Corporation. All rights reserved. * http://www.jaspersoft.com * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is part of JasperReports. * * JasperReports 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 3 of the License, or * (at your option) any later version. * * JasperReports 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. * * You should have received a copy of the GNU Lesser General Public License * along with JasperReports. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.jasperreports.engine.xml; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import net.sf.jasperreports.engine.JRRuntimeException; import net.sf.jasperreports.engine.component.ComponentsBundle; import net.sf.jasperreports.engine.component.ComponentsEnvironment; import net.sf.jasperreports.engine.component.ComponentsXmlParser; import net.sf.jasperreports.engine.util.ClassUtils; import net.sf.jasperreports.engine.util.JRLoader; import net.sf.jasperreports.engine.util.JRProperties; import org.apache.commons.collections.ReferenceMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.SAXException; /** * The default report SAX parser factory. * * <p> * This factory creates a parser via the default SAX parser factory * (<code>javax.xml.parsers.SAXParserFactory.newInstance()</code>). * * <p> * JRXMLs are always validated using W3C XML schemas. Reports that refer * the JasperReports DTD (which has been deprecated) are validated using an * internal XML schema equivalent to the DTD. * * <p> * To improve performance, XML schemas can be cached when using a Xerces * SAX parser. See {@link #PROPERTY_CACHE_SCHEMAS}. * * @author Lucian Chirita (lucianc@users.sourceforge.net) * @version $Id: JRReportSaxParserFactory.java 3657 2010-03-31 09:34:49Z teodord $ */ public class JRReportSaxParserFactory implements JRSaxParserFactory { private static final Log log = LogFactory.getLog(JRXmlDigesterFactory.class); /** * A property that determines whether XML schemas/grammars are to be cached * so that they are not read/initialized each time a report is compiled. * * <p> * Currently, setting this property is only effective when a Xerces XML * parser is used (either a stock one from Apache or one embedded into a * SUN JDK). */ public static final String PROPERTY_CACHE_SCHEMAS = JRProperties.PROPERTY_PREFIX + "compiler.xml.parser.cache.schemas"; protected static final String PACKAGE_PREFIX_XERCES = "org.apache.xerces"; protected static final String POOL_CLASS_XERCES = "org.apache.xerces.util.XMLGrammarPoolImpl"; protected static final String PACKAGE_PREFIX_SUN_XERCES = "com.sun.org.apache.xerces"; protected static final String POOL_CLASS_SUN_XERCES = "com.sun.org.apache.xerces.internal.util.XMLGrammarPoolImpl"; protected static final String XERCES_PARSER_PROPERTY_GRAMMAR_POOL = "http://apache.org/xml/properties/internal/grammar-pool"; private final static Object GRAMMAR_POOL_CACHE_NULL_KEY = "Null context classloader"; private final static ThreadLocal GRAMMAR_POOL_CACHE = new ThreadLocal(); public SAXParser createParser() { try { SAXParserFactory parserFactory = createSAXParserFactory(); SAXParser parser = parserFactory.newSAXParser(); configureParser(parser); return parser; } catch (SAXException e) { throw new JRRuntimeException("Error creating SAX parser", e); } catch (ParserConfigurationException e) { throw new JRRuntimeException("Error creating SAX parser", e); } } protected SAXParserFactory createSAXParserFactory() throws ParserConfigurationException, SAXException { SAXParserFactory parserFactory = SAXParserFactory.newInstance(); if (log.isDebugEnabled()) { log.debug("Instantiated SAX parser factory of type " + parserFactory.getClass().getName()); } parserFactory.setNamespaceAware(true); boolean validating = JRProperties.getBooleanProperty(JRProperties.COMPILER_XML_VALIDATION); parserFactory.setValidating(validating); parserFactory.setFeature("http://xml.org/sax/features/validation", validating); return parserFactory; } protected void configureParser(SAXParser parser) throws SAXException { List schemaLocations = getSchemaLocations(); parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", schemaLocations.toArray(new String[schemaLocations.size()])); boolean cache = JRProperties.getBooleanProperty(PROPERTY_CACHE_SCHEMAS); if (cache) { enableSchemaCaching(parser); } } protected List getSchemaLocations() { List schemas = new ArrayList(); schemas.add(getResourceURI(JRXmlConstants.JASPERREPORT_XSD_RESOURCE)); schemas.add(getResourceURI(JRXmlConstants.JASPERREPORT_XSD_DTD_COMPAT_RESOURCE)); Collection components = ComponentsEnvironment.getComponentBundles(); for (Iterator it = components.iterator(); it.hasNext();) { ComponentsBundle componentManager = (ComponentsBundle) it.next(); ComponentsXmlParser xmlParser = componentManager.getXmlParser(); String schemaURI; String schemaResource = xmlParser.getInternalSchemaResource(); if (schemaResource != null) { schemaURI = getResourceURI(schemaResource); } else { schemaURI = xmlParser.getPublicSchemaLocation(); } if (log.isDebugEnabled()) { log.debug("Adding components schema at " + schemaURI); } schemas.add(schemaURI); } return schemas; } protected String getResourceURI(String resource) { URL location = JRLoader.getResource(resource); if (location == null) { throw new JRRuntimeException("Could not find resource " + resource); } return location.toExternalForm(); } protected void enableSchemaCaching(SAXParser parser) { String parserType = parser.getClass().getName(); if (parserType.startsWith(PACKAGE_PREFIX_XERCES)) { setGrammarPoolProperty(parser, POOL_CLASS_XERCES); } else if (parserType.startsWith(PACKAGE_PREFIX_SUN_XERCES)) { setGrammarPoolProperty(parser, POOL_CLASS_SUN_XERCES); } else { if (log.isDebugEnabled()) { log.debug("Schema caching only works with Xerces parsers"); } } } protected void setGrammarPoolProperty(SAXParser parser, String poolClassName) { try { Object cacheKey = getGrammarPoolCacheKey(); // we're using thread local caches to avoid thread safety problems ReferenceMap cacheMap = (ReferenceMap) GRAMMAR_POOL_CACHE.get(); if (cacheMap == null) { cacheMap = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.SOFT); GRAMMAR_POOL_CACHE.set(cacheMap); } Object grammarPool = cacheMap.get(cacheKey); if (grammarPool == null) { if (log.isDebugEnabled()) { log.debug("Instantiating grammar pool of type " + poolClassName + " for cache key " + cacheKey); } grammarPool = ClassUtils.instantiateClass(poolClassName, Object.class); cacheMap.put(cacheKey, grammarPool); } parser.setProperty(XERCES_PARSER_PROPERTY_GRAMMAR_POOL, grammarPool); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("Error setting Xerces grammar pool of type " + poolClassName, e); } } } protected Object getGrammarPoolCacheKey() { Object key = Thread.currentThread().getContextClassLoader(); if (key == null) { key = GRAMMAR_POOL_CACHE_NULL_KEY; } return key; } }