/** * Copyright 2011 meltmedia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xchain.framework.net; import java.util.List; import java.util.ArrayList; import java.util.LinkedList; import java.util.Set; import java.util.HashSet; import java.util.Collections; import java.net.URL; import javax.xml.transform.Source; import javax.xml.transform.URIResolver; import javax.xml.transform.TransformerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * DependencyTracker is used to track dependencies. Dependency tracking is thread safe, but a thread can only track dependencies for a single entity at a time. * Dependency tracking is started with the startTracking() method. Dependencies will be registered with dependencyFound() or dependencySetFound methods or * when a URI is resolved with a wrapped URIResolver. Wrapped URIResolvers are created with the createDependencyUriResolver() method. * Dependency tracking is stopped with the stopTracking() method. This method will return a set of URLs which are the tracked dependencies. * * @author Christian Trimble * @author Devon Tackett * @author Josh Kennedy */ public class DependencyTracker { protected static Logger log = LoggerFactory.getLogger( DependencyTracker.class ); protected static DependencyTracker instance = new DependencyTracker(); private DependencyTracker() { } /** * @return The current instance of the DependencyTracker. */ public static DependencyTracker getInstance() { return instance; } protected ThreadLocal<LinkedList<List<URL>>> dependencyListStackThreadLocal = new ThreadLocal<LinkedList<List<URL>>>() { protected LinkedList<List<URL>> initialValue() { return new LinkedList<List<URL>>(); } }; protected LinkedList<List<URL>> getDependencyListStack() { return dependencyListStackThreadLocal.get(); } /** * Start tracking dependency usages on the current thread. */ public void startTracking() { LinkedList<List<URL>> dependencyListStack = getDependencyListStack(); List<URL> dependencyList = null; if( dependencyListStack.isEmpty() ) { dependencyList = new ArrayList<URL>(); } else { dependencyList = (List<URL>)dependencyListStack.getFirst(); int size = dependencyList.size(); dependencyList = dependencyList.subList( size, size ); } dependencyListStack.addFirst(dependencyList); } /** * Stop tracking dependencies on the current thread. * * @return The set of URL dependencies tracked. */ public Set<URL> stopTracking() { LinkedList<List<URL>> dependencyListStack = getDependencyListStack(); if( !dependencyListStack.isEmpty() ) { List<URL> dependencyList = dependencyListStack.removeFirst(); return new HashSet<URL>(dependencyList); } else { return Collections.EMPTY_SET; } } /** * Create a wrapped URIResolver for dependency tracking. Any URLs resolved through * the returned URIResolve will be considered dependencies while tracking is on. * * @param uriResolver The original URI resolver to wrap. * * @return The wrapped URI resolver. */ public URIResolver createDependencyUriResolver( URIResolver uriResolver ) { DependencyUriResolver dependencyUriResolver = null; if( uriResolver != null && uriResolver instanceof DependencyUriResolver ) { dependencyUriResolver = (DependencyUriResolver)uriResolver; } else { dependencyUriResolver = new DependencyUriResolver(uriResolver); } return dependencyUriResolver; } /** * Register the given URL as a dependency. * * @param dependency The dependency URL. */ public void dependencyFound( URL dependency ) { LinkedList<List<URL>> dependencyListStack = getDependencyListStack(); if( !dependencyListStack.isEmpty() ) { List<URL> dependencyList = dependencyListStack.getFirst(); if( dependencyList != null ) { dependencyList.add(dependency); } } } /** * Register the given set of dependency URLs. * * @param dependencySet The set of dependencies to register. */ public void dependencySetFound( Set<URL> dependencySet ) { LinkedList<List<URL>> dependencyListStack = getDependencyListStack(); if( !dependencyListStack.isEmpty() ) { List<URL> dependencyList = dependencyListStack.getFirst(); if( dependencyList != null ) { dependencyList.addAll(dependencySet); } } } /** * Wrapped URIResolver to track dependencies. */ public class DependencyUriResolver implements URIResolver { public URIResolver wrapped; public DependencyUriResolver( URIResolver wrapped ) { this.wrapped = wrapped; } /** * Resolve the given URI. The URI will be considered a dependency if used while dependency tracking is on. */ public Source resolve( String href, String base ) throws TransformerException { if( log.isDebugEnabled() ) { log.debug("Tracking href '"+href+"' and base '"+base+"'."); } // get the url. try { URL dependency = UrlFactory.getInstance().newUrl( base, href ); // track the url. dependencyFound( dependency ); } catch( Exception e ) { throw new TransformerException("Could not track dependency for base '"+base+"' and href '"+href+"'.", e); } // return the value of the wrapped resolver. if( wrapped != null ) { return wrapped.resolve( href, base ); } else { return null; } } } }