in tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AssetSourceImpl.java [164:281]
public Asset getComponentAsset(final ComponentResources resources, final String path, final String libraryName)
{
assert resources != null;
assert InternalUtils.isNonBlank(path);
return tracker.invoke(String.format("Resolving '%s' for component %s", path, resources.getCompleteId()),
new Invokable<Asset>()
{
public Asset invoke()
{
// First, expand symbols:
String expanded = symbolSource.expandSymbols(path);
int dotx = expanded.indexOf(':');
// We special case the hell out of 'classpath:' so that we can provide warnings today (5.4) and
// blow up in a useful fashion tomorrow (5.5).
if (expanded.startsWith("//") || (dotx > 0 && !expanded.substring(0, dotx).equalsIgnoreCase(AssetConstants.CLASSPATH)))
{
final String prefix = dotx >= 0 ? expanded.substring(0, dotx) : AssetConstants.PROTOCOL_RELATIVE;
if (EXTERNAL_URL_PREFIXES.contains(prefix))
{
String url;
if (prefix.equals(AssetConstants.PROTOCOL_RELATIVE))
{
url = (request != null && request.isSecure() ? "https:" : "http:") + expanded;
url = url.replace("//:", "//");
} else
{
url = expanded;
}
try
{
UrlResource resource = new UrlResource(new URL(url));
return new UrlAsset(url, resource);
} catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
} else
{
return getAssetInLocale(resources.getBaseResource(), expanded, resources.getLocale());
}
}
// No prefix, so implicitly classpath:, or explicitly classpath:
String restOfPath = expanded.substring(dotx + 1);
// This is tricky, because a relative path (including "../") is ok in 5.3, since its just somewhere
// else on the classpath (though you can "stray" out of the "safe" zone). In 5.4, under /META-INF/assets/
// it's possible to "stray" out beyond the safe zone more easily, into parts of the classpath that can't be
// represented in the URL.
// Ends with trailing slash:
String metaRoot = "META-INF/assets/" + toPathPrefix(libraryName);
String trimmedRestOfPath = restOfPath.startsWith("/") ? restOfPath.substring(1) : restOfPath;
// TAP5-2044: Some components specify a full path, starting with META-INF/assets/, and we should just trust them.
// The warning logic below is for compnents that specify a relative path. Our bad decisions come back to haunt us;
// Resource paths should always had a leading slash to differentiate relative from complete.
String metaPath = trimmedRestOfPath.startsWith("META-INF/assets/") ? trimmedRestOfPath : metaRoot + trimmedRestOfPath;
// Based on the path, metaResource is where it should exist in a 5.4 and beyond world ... unless the expanded
// path was a bit too full of ../ sequences, in which case the expanded path is not valid and we adjust the
// error we write.
Resource metaResource = findLocalizedResource(null, metaPath, resources.getLocale());
Asset result = getComponentAsset(resources, expanded, metaResource);
if (result == null)
{
throw new RuntimeException(String.format("Unable to locate asset '%s' for component %s. It should be located at %s.",
path, resources.getCompleteId(),
metaPath));
}
// This is the best way to tell if the result is an asset for a Classpath resource.
Resource resultResource = result.getResource();
if (!resultResource.equals(metaResource))
{
if (firstWarning.getAndSet(false))
{
logger.error("Packaging of classpath assets has changed in release 5.4; " +
"Assets should no longer be on the main classpath, " +
"but should be moved to 'META-INF/assets/' or a sub-folder. Future releases of Tapestry may " +
"no longer support assets on the main classpath.");
}
if (metaResource.getFolder().startsWith(metaRoot))
{
logger.warn(String.format("Classpath asset '/%s' should be moved to folder '/%s/'.",
resultResource.getPath(),
metaResource.getFolder()));
} else
{
logger.warn(String.format("Classpath asset '/%s' should be moved under folder '/%s', and the relative path adjusted.",
resultResource.getPath(),
metaRoot));
}
}
return result;
}
}
);
}