/*
* 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;
}
}
}