/**
* 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.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.net.MalformedURLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.PatternSyntaxException;
import java.util.Map;
import java.util.HashMap;
import org.xchain.framework.net.protocol.resource.ResourceUrlStreamHandlerFactory;
import org.xchain.framework.net.protocol.http.HttpUrlStreamHandlerFactory;
/**
* This factory provides a mechanism to register url protocols when you cannot set the URLStreamHandlerFactory on the URL class.
* This occurs in Web Applications, since most containers set the URLStreamHandlerFactory when they initialize.
*
* When used in a Web Application, this class should always reside in the WEB-INF/lib directory of the application. If this class were
* deployed in the containers class loader, then protocols would be registered across web applications and this is not the desired effect
* of this class.
*
* @author Christian Trimble
* @author Jason Rose
* @author Josh Kennedy
*/
public final class UrlFactory
{
/** The log for this class. */
public static Logger log = LoggerFactory.getLogger(UrlFactory.class);
/** The pattern object for parsing url protocols. */
protected static Pattern protocolPattern = null;
/** The pattern string for parsing url protocols. */
protected static String protocolPatternString = "\\A([a-zA-Z][-a-zA-Z0-9\\.\\+]+):.*\\Z";
/* Compile the protocol pattern. */
static {
try {
protocolPattern = Pattern.compile(protocolPatternString);
}
catch( PatternSyntaxException pse ) {
log.error( "Could not compile protocol pattern '"+protocolPatternString+"'.", pse );
}
}
/** The singleton instance of this class. */
private static UrlFactory instance = new UrlFactory();
static {
try {
// NOTE: This should be done using xml configuration files.
Map<String, URLStreamHandlerFactory> protocolMap = UrlFactory.getInstance().getProtocolMap();
synchronized( protocolMap ) {
protocolMap.put(ResourceUrlStreamHandlerFactory.RESOURCE_PROTOCOL, new ResourceUrlStreamHandlerFactory());
protocolMap.put(HttpUrlStreamHandlerFactory.HTTP_PROTOCOL, new HttpUrlStreamHandlerFactory());
}
if( log.isDebugEnabled() ) {
log.debug("Loaded protocol '"+ResourceUrlStreamHandlerFactory.RESOURCE_PROTOCOL+"'.");
}
}
catch( Exception e ) {
log.error( "Could not load default protocols.", e );
}
}
/**
* Returns the UrlFactory singleton.
*/
public static UrlFactory getInstance()
{
return instance;
}
/**
* This URLStreamHandlerFactory maps protocols to URLStreamHandlerFactories. This allows overriding in cases where one handler factory
* implements several protocols.
*/
protected MappedUrlStreamHandlerFactory factory = null;
/**
* The map of protocols currently defined for this url factory.
*/
protected Map<String, URLStreamHandlerFactory> protocolMap = java.util.Collections.synchronizedMap(new HashMap<String, URLStreamHandlerFactory>());
private UrlFactory()
{
this.factory = new MappedUrlStreamHandlerFactory();
}
/**
* Returns the mapping of protocols to URLStreamHandlerFactory classes. This mapping is synchronized with the java.util.Collections class, please
* refer to its documentation when editing this map.
*/
public Map<String, URLStreamHandlerFactory> getProtocolMap()
{
return protocolMap;
}
/**
* Creates a new url based on a spec.
*/
public URL newUrl( String spec )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl(String spec)");
}
return newUrl( (URL)null, spec, (URLStreamHandler)null );
}
public URL newUrl( String spec, URLStreamHandler handler )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String spec, URLStreamHandler handler )");
}
return newUrl( (URL)null, spec, handler );
}
/**
* Creates a new URL based on the parts of a spec.
*/
public URL newUrl( String protocol, String host, int port, String file)
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String protocol, String host, int port, String file)");
}
// return a new url with this handler.
return newUrl( protocol, host, port, file, (URLStreamHandler)null );
}
public URL newUrl( String protocol, String host, int port, String file, URLStreamHandler handler )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String protocol, String host, int port, String file, URLStreamHandler handler )");
}
return new URL( protocol, host, port, file, lookupUrlStreamHandler(protocol, handler) );
}
/**
* Creates a new URL based on the parts of a spec.
*/
public URL newUrl( String protocol, String host, String file )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String protocol, String host, String file )");
}
// return a new url with this handler.
return newUrl( protocol, host, -1, file, (URLStreamHandler)null );
}
public URL newUrl( String protocol, String host, String file, URLStreamHandler handler )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String protocol, String host, String file, URLStreamHandler handler )");
}
return newUrl( protocol, host, -1, file, handler );
}
/**
* This method loads the URLStreamHandler for the spec (or context if the spec is relative) and then passes them
* to the newUrl( URL, String, URLStreamHandler ) method.
*/
public URL newUrl( URL context, String spec )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( URL context, String spec )");
}
// return a new url with this handler.
return newUrl( context, spec, (URLStreamHandler)null );
}
/**
* Returns a new URL object by passing these arguments to the matching URL constructor.
*/
public URL newUrl( URL context, String spec, URLStreamHandler handler )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( URL context, String spec, URLStreamHandler handler )");
}
// find the protocol.
String protocol = parseProtocol(spec, false);
if( protocol == null && context != null ) {
protocol = context.getProtocol();
}
if( protocol == null ) {
throw new MalformedURLException("No protocol specified.");
}
// create the url.
return new URL( (URL)null, RelativeUrlUtil.resolve(context, spec), lookupUrlStreamHandler( protocol, handler ) );
}
/**
* This method creates a URL object for the supplied context and then passes the arguments to the
* newUrl( URL, String ) method.
*/
public URL newUrl( String context, String spec )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String context, String spec )");
}
URL contextUrl = null;
if( context != null ) {
contextUrl = newUrl(context);
}
// return a new url with this handler.
return newUrl(contextUrl, spec, (URLStreamHandler)null );
}
/**
* This method creates a new URL object for the context using the provided handler and then passes the
* aguments to the newUrl( URL, String, URLStreamHandler ) method to create the URL.
*/
public URL newUrl( String context, String spec, URLStreamHandler handler )
throws MalformedURLException
{
if( log.isDebugEnabled() ) {
log.debug("newUrl( String context, String spec, URLStreamHandler handler )");
}
// create a url object for the context.
URL contextUrl = null;
if( context != null ) {
contextUrl = newUrl( context, handler );
}
// call newUrl( URL, String, URLStreamHandler )
return newUrl( contextUrl, spec, handler );
}
/**
* Returns the url stream handler to pass to new URL(). If the handler is not null, then
* the handler is returned. If the handler is null, then the registered handler for the
* specified protocol is returned. If the handler is null and there is no handler registered
* for the protocol, then null is returned.
*/
protected URLStreamHandler lookupUrlStreamHandler( String protocol, URLStreamHandler handler )
{
if( handler != null ) {
return handler;
}
if( factory != null ) {
return factory.createURLStreamHandler(protocol);
}
return null;
}
/**
* Parses the protocol from the spec. This method supports the scheme syntax found in rfc2396.
* If a valid protocol cannot be parsed from the provided spec, then null is returned.
*/
public String parseProtocol( String spec )
throws MalformedURLException
{
return parseProtocol( spec, false );
}
public String parseProtocol( String spec, boolean malformedIfNoProtocol )
throws MalformedURLException
{
Matcher protocolMatcher = protocolPattern.matcher( spec );
if( protocolMatcher.find() ) {
return protocolMatcher.group(1);
}
else if( malformedIfNoProtocol ) {
throw new MalformedURLException("Could not parse protocol from url '"+spec+"'.");
}
else {
return null;
}
}
/**
* The class used to route protocols to the proper URLStreamHandlerFactory.
*/
public class MappedUrlStreamHandlerFactory
implements URLStreamHandlerFactory
{
private MappedUrlStreamHandlerFactory()
{
}
public URLStreamHandler createURLStreamHandler( String protocol )
{
URLStreamHandler handler = null;
URLStreamHandlerFactory targetFactory = protocolMap.get(protocol);
if( targetFactory != null ) {
handler = targetFactory.createURLStreamHandler(protocol);
}
return handler;
}
}
}