blob: faa8d991164282d47813441544331140956908d0 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.quickstart;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
/**
* Normalize Attribute to String.
* <p>
* Replaces and expands:
* <ul>
* <li>${WAR}</li>
* <li>${WAR.path}</li>
* <li>${WAR.uri}</li>
* <li>${jetty.base}</li>
* <li>${jetty.base.uri}</li>
* <li>${jetty.home}</li>
* <li>${jetty.home.uri}</li>
* <li>${user.home}</li>
* <li>${user.home.uri}</li>
* <li>${user.dir}</li>
* <li>${user.dir.uri}</li>
* </ul>
*/
public class AttributeNormalizer
{
private static final Logger LOG = Log.getLogger(AttributeNormalizer.class);
private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}");
private static class Attribute
{
final String key;
final String value;
final int weight;
public Attribute(String key, String value, int weight)
{
this.key = key;
this.value = value;
this.weight = weight;
}
}
private static URI toCanonicalURI(URI uri)
{
uri = uri.normalize();
String ascii = uri.toASCIIString();
if (ascii.endsWith("/"))
{
try
{
uri = new URI(ascii.substring(0,ascii.length()-1));
}
catch(URISyntaxException e)
{
throw new IllegalArgumentException(e);
}
}
return uri;
}
private static Path toCanonicalPath(String path)
{
if (path == null)
return null;
if (path.length()>1 && path.endsWith("/"))
path = path.substring(0,path.length()-1);
return toCanonicalPath(FileSystems.getDefault().getPath(path));
}
private static Path toCanonicalPath(Path path)
{
if (path == null)
{
return null;
}
if (Files.exists(path))
{
try
{
return path.toRealPath();
}
catch (IOException e)
{
throw new IllegalArgumentException(e);
}
}
return path.toAbsolutePath();
}
private static class PathAttribute extends Attribute
{
public final Path path;
public PathAttribute(String key, Path path, int weight)
{
super(key,path.toString(),weight);
this.path = path;
}
@Override
public String toString()
{
return String.format("PathAttribute[%s=>%s]",key,path);
}
}
private static class URIAttribute extends Attribute
{
public final URI uri;
public URIAttribute(String key, URI uri, int weight)
{
super(key,uri.toASCIIString(),weight);
this.uri = uri;
}
@Override
public String toString()
{
return String.format("URIAttribute[%s=>%s]",key,uri);
}
}
private static Comparator<Attribute> attrComparator = new Comparator<Attribute>()
{
@Override
public int compare(Attribute o1, Attribute o2)
{
if( (o1.value == null) && (o2.value != null) )
{
return -1;
}
if( (o1.value != null) && (o2.value == null) )
{
return 1;
}
if( (o1.value == null) && (o2.value == null) )
{
return 0;
}
// Different lengths?
int diff = o2.value.length() - o1.value.length();
if(diff != 0)
{
return diff;
}
// Different names?
diff = o2.value.compareTo(o1.value);
if(diff != 0)
{
return diff;
}
// The paths are the same, base now on weight
return o2.weight - o1.weight;
}
};
private static void add(List<PathAttribute>paths,List<URIAttribute> uris,String key,int weight)
{
String value = System.getProperty(key);
if (value!=null)
{
Path path = toCanonicalPath(value);
paths.add(new PathAttribute(key,path,weight));
uris.add(new URIAttribute(key+".uri",toCanonicalURI(path.toUri()),weight));
}
}
private URI warURI;
private Map<String,Attribute> attributes = new HashMap<>();
private List<PathAttribute> paths = new ArrayList<>();
private List<URIAttribute> uris = new ArrayList<>();
public AttributeNormalizer(Resource baseResource)
{
if (baseResource==null)
throw new IllegalArgumentException("No base resource!");
warURI = toCanonicalURI(baseResource.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
add(paths,uris,"jetty.base",9);
add(paths,uris,"jetty.home",8);
add(paths,uris,"user.home",7);
add(paths,uris,"user.dir",6);
if (warURI.getScheme().equalsIgnoreCase("file"))
paths.add(new PathAttribute("WAR.path",toCanonicalPath(new File(warURI).toString()),10));
uris.add(new URIAttribute("WAR.uri", warURI,9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI,8)); // legacy encoding
Collections.sort(paths,attrComparator);
Collections.sort(uris,attrComparator);
Stream.concat(paths.stream(),uris.stream()).forEach(a->attributes.put(a.key,a));
if (LOG.isDebugEnabled())
{
for (Attribute attr : attributes.values())
{
LOG.debug(attr.toString());
}
}
}
/**
* Normalize a URI, URL, or File reference by replacing known attributes with ${key} attributes.
*
* @param o the object to normalize into a string
* @return the string representation of the object, with expansion keys.
*/
public String normalize(Object o)
{
try
{
// Find a URI
URI uri = null;
Path path = null;
if (o instanceof URI)
uri = toCanonicalURI(((URI)o));
else if (o instanceof Resource)
uri = toCanonicalURI(((Resource)o).getURI());
else if (o instanceof URL)
uri = toCanonicalURI(((URL)o).toURI());
else if (o instanceof File)
path = ((File)o).getAbsoluteFile().getCanonicalFile().toPath();
else if (o instanceof Path)
path = (Path)o;
else
{
String s = o.toString();
try
{
uri = new URI(s);
if (uri.getScheme() == null)
{
// Unknown scheme? not relevant to normalize
return s;
}
}
catch(URISyntaxException e)
{
// This path occurs for many reasons, but most common is when this
// is executed on MS Windows, on a string like "D:\jetty"
// and the new URI() fails for
// java.net.URISyntaxException: Illegal character in opaque part at index 2: D:\jetty
return s;
}
}
if (uri!=null)
{
if ("jar".equalsIgnoreCase(uri.getScheme()))
{
String raw = uri.getRawSchemeSpecificPart();
int bang = raw.indexOf("!/");
String normal = normalize(raw.substring(0,bang));
String suffix = raw.substring(bang);
return "jar:" + normal + suffix;
}
else
{
if(uri.isAbsolute())
{
return normalizeUri(uri);
}
}
}
else if (path!=null)
return normalizePath(path);
}
catch (Exception e)
{
LOG.warn(e);
}
return String.valueOf(o);
}
protected String normalizeUri(URI uri)
{
for (URIAttribute a : uris)
{
try
{
if (uri.compareTo(a.uri)==0)
return String.format("${%s}",a.key);
if (!a.uri.getScheme().equalsIgnoreCase(uri.getScheme()))
continue;
if (a.uri.getHost()==null && uri.getHost()!=null)
continue;
if (a.uri.getHost()!=null && !a.uri.getHost().equals(uri.getHost()))
continue;
if (a.uri.getPath().equals(uri.getPath()))
return a.value;
if (!uri.getPath().startsWith(a.uri.getPath()))
continue;
String s = uri.getPath().substring(a.uri.getPath().length());
if (s.charAt(0)!='/')
continue;
return String.format("${%s}%s",a.key,new URI(s).toASCIIString());
}
catch(URISyntaxException e)
{
LOG.ignore(e);
}
}
return uri.toASCIIString();
}
protected String normalizePath(Path path)
{
for (PathAttribute a : paths)
{
try
{
if (path.equals(a.path) || Files.isSameFile(path,a.path))
return String.format("${%s}",a.key);
}
catch (IOException ignore)
{
LOG.ignore(ignore);
}
if (path.startsWith(a.path))
return String.format("${%s}%c%s",a.key,File.separatorChar,a.path.relativize(path).toString());
}
return path.toString();
}
public String expand(String str)
{
return expand(str,new Stack<String>());
}
public String expand(String str, Stack<String> seenStack)
{
if (str == null)
{
return str;
}
if (str.indexOf("${") < 0)
{
// Contains no potential expressions.
return str;
}
Matcher mat = __propertyPattern.matcher(str);
StringBuilder expanded = new StringBuilder();
int offset = 0;
String property;
String value;
while (mat.find(offset))
{
property = mat.group(1);
// Loop detection
if (seenStack.contains(property))
{
StringBuilder err = new StringBuilder();
err.append("Property expansion loop detected: ");
int idx = seenStack.lastIndexOf(property);
for (int i = idx; i < seenStack.size(); i++)
{
err.append(seenStack.get(i));
err.append(" -> ");
}
err.append(property);
throw new RuntimeException(err.toString());
}
seenStack.push(property);
// find property name
expanded.append(str.subSequence(offset,mat.start()));
// get property value
value = getString(property);
if (value == null)
{
if(LOG.isDebugEnabled())
LOG.debug("Unable to expand: {}",property);
expanded.append(mat.group());
}
else
{
// recursively expand
value = expand(value,seenStack);
expanded.append(value);
}
// update offset
offset = mat.end();
}
// leftover
expanded.append(str.substring(offset));
// special case for "$$"
if (expanded.indexOf("$$") >= 0)
{
return expanded.toString().replaceAll("\\$\\$","\\$");
}
return expanded.toString();
}
private String getString(String property)
{
if(property==null)
{
return null;
}
Attribute a = attributes.get(property);
if (a!=null)
return a.value;
// Use system properties next
return System.getProperty(property);
}
}