/* * Reference ETL Parser for Java * Copyright (c) 2000-2009 Constantine A Plotnikov * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.sf.etl.parsers.internal.term_parser; import java.net.URI; import java.net.URISyntaxException; import java.util.Hashtable; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.etl.parsers.ErrorInfo; import net.sf.etl.parsers.StandardGrammars; import net.sf.etl.parsers.internal.term_parser.cache.LocatorCacheFacet; import net.sf.etl.parsers.internal.term_parser.cache.LocatorCacheManager; import net.sf.etl.parsers.internal.term_parser.compiler.GrammarAssemblyBuilder; import net.sf.etl.parsers.internal.term_parser.states.StateMachinePeerFactory; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; /** * <p> * A simple grammar locator that maintains cache of loaded grammars in the * memory. This implementation of locator never forgets loaded grammars. It * should be only used if it is expected that grammars are stable while this * locator is used (for example during compilation process). * <p> * * @author const * */ // NOTE PRE 0.3: the more I look to it, the more locators look like garbage. // They should be reworked. public class DefaultGrammarLocator implements GrammarLocator, LocatorCacheFacet { /** a logger used by this class to log the problems */ private static final Logger log = Logger .getLogger(DefaultGrammarLocator.class.getName()); /** * A manager for cache lock */ private final Object cacheManagerLock = new Object(); /** * lock for the grammar cache */ private final Object cacheLock = new Object(); /** * Peer factories that has been already resolved */ private final Hashtable<String, PeerFactory> peersBySystemId = new Hashtable<String, PeerFactory>(); { peersBySystemId.put(StandardGrammars.DEFAULT_GRAMMAR_SYSTEM_ID, DefaultGrammarPeerFactory.INSTANCE); } /** cache manager */ private LocatorCacheManager cacheManager; /** a default entity resolver */ private EntityResolver defaultEntityResolver; /** * A constructor */ public DefaultGrammarLocator() { super(); } /** * {@inheritDoc} */ public PeerFactory getGrammar(DefaultTermParser termParser, String sourceSystemId, String grammarSystemId, String grammarPublicId, String requiredContext) { PeerFactory rc = null; final InputSource grammarSource = resolveGrammar(termParser, sourceSystemId, grammarSystemId, grammarPublicId); try { if (grammarSource.getSystemId() != null) { synchronized (cacheLock) { rc = peersBySystemId.get(grammarSource.getSystemId()); } if (rc != null && rc.supportsStartContext(requiredContext)) { return rc; } } if (rc == null) { final GrammarAssemblyBuilder builder = GrammarAssemblyBuilder .build(this, termParser, grammarSource); if (null == builder || builder.hadErrors()) { // ignore results of builder if there were errors return DefaultGrammarPeerFactory.INSTANCE; } final StateMachinePeerFactory sm = builder.getRootPeerFactory(); if (sm != null && sm.supportsStartContext(requiredContext)) { return sm; } else { termParser.reportDoctypeError( "grammar.Doctype.noRequiredContext", new Object[] { grammarSource.getSystemId(), requiredContext }); } } termParser.reportDoctypeError( "grammar.Doctype.usingDefaultGrammar", ErrorInfo.NO_ARGS); return DefaultGrammarPeerFactory.INSTANCE; } finally { try { if (grammarSource.getByteStream() != null) { grammarSource.getByteStream().close(); } } catch (final Throwable ex) { // exception is ok because stream is likely closed } try { if (grammarSource.getCharacterStream() != null) { grammarSource.getCharacterStream().close(); } } catch (final Throwable ex) { // exception is ok because stream is likely closed } } } /** * {@inheritDoc} */ public void registerPeer(String systemId, String publicId, PeerFactory factory) { if (systemId != null) { try { final URI uri = URI.create(systemId); if (uri.isAbsolute() || uri.isOpaque()) { synchronized (cacheLock) { peersBySystemId.put(systemId, factory); } } } catch (final Exception ex) { // just ignore URI if it is invalid } } } /** * {@inheritDoc} */ public InputSource resolveGrammar(DefaultTermParser termParser, String referrerSystemId, String systemId, String publicId) { systemId = resolveSystemId(referrerSystemId, systemId); if (systemId == null && publicId != null) { systemId = resolvePublicId(publicId); } InputSource source = null; EntityResolver resolver = termParser.getEntityResolver(); if (resolver == null) { synchronized (cacheManagerLock) { resolver = defaultEntityResolver; } } if (resolver != null) { try { source = resolver.resolveEntity(publicId, systemId); } catch (final Exception ex) { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "resolver had thrown an exception", ex); } } } if (source == null) { source = new InputSource(); source.setSystemId(systemId); source.setPublicId(publicId); } return source; } /** * Attempt to resolve a public id without consulting entity resolver. This * method is overridden in subclasses. * * @param publicId * a publicId to resolve * @return a system id */ protected String resolvePublicId(String publicId) { if (StandardGrammars.ETL_GRAMMAR_PUBLIC_ID.equals(publicId)) { return StandardGrammars.ETL_GRAMMAR_SYSTEM_ID; } return null; } /** * Resolve systemId relatively to base URL * * @param referrerSystemId * a referrer systemId * @param systemId * a specified systemId * @return a resolved systemId */ private String resolveSystemId(String referrerSystemId, String systemId) { if (referrerSystemId != null && systemId != null) { try { final URI sourceURI = new URI(referrerSystemId); if (sourceURI.isAbsolute() && !sourceURI.isOpaque()) { final URI systemURI = sourceURI.resolve(systemId); systemId = systemURI.toString(); } } catch (final URISyntaxException e) { // do nothing systemId is left as it is } } return systemId; } /** * Change cache manager * * @param manager * a manager to install. */ public void setCacheManager(LocatorCacheManager manager) { synchronized (cacheManagerLock) { if (this.cacheManager != null) { try { manager.cacheManagerUninstalled(this); } catch (final Throwable e) { log .log(Level.SEVERE, "Locator had problem unsintalling", e); } } this.cacheManager = manager; if (this.cacheManager != null) { try { manager.cacheManagerInstalled(this); } catch (final Throwable e) { log.log(Level.SEVERE, "Locator had problem intalling", e); this.cacheManager = null; } } } } /** * {@inheritDoc} */ public void invalidate(String systemId, String publicId) { synchronized (cacheLock) { peersBySystemId.remove(systemId); } } /** * Set default entity resolver. It is usually set by the same program that * installs cache manager. * * @param resolver * a resolver */ public void setDefaultEntityResolver(EntityResolver resolver) { synchronized (cacheManagerLock) { defaultEntityResolver = resolver; } } }