001// Copyright 2006-2014 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.services;
016
017import org.apache.tapestry5.commons.Resource;
018import org.apache.tapestry5.commons.util.AvailableValues;
019import org.apache.tapestry5.commons.util.CollectionFactory;
020import org.apache.tapestry5.commons.util.UnknownValueException;
021import org.apache.tapestry5.services.AssetAlias;
022import org.apache.tapestry5.services.ClasspathAssetAliasManager;
023
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.List;
027import java.util.Map;
028
029public class ClasspathAssetAliasManagerImpl implements ClasspathAssetAliasManager
030{
031    /**
032     * Map from alias to path.
033     */
034    private final Map<String, String> aliasToPathPrefix = CollectionFactory.newMap();
035
036    /**
037     * Map from path to alias.
038     */
039    private final Map<String, String> pathPrefixToAlias = CollectionFactory.newMap();
040
041    private final List<String> sortedPathPrefixes;
042
043    /**
044     * Configuration is a map of aliases (short names) to complete names. Keys and values must not start or end with a slash,
045     * but may contain them.
046     */
047    public ClasspathAssetAliasManagerImpl(Map<String, String> configuration)
048    {
049        for (Map.Entry<String, String> e : configuration.entrySet())
050        {
051            String alias = verify("folder name", e.getKey());
052
053            String path = verify("path", e.getValue());
054
055            aliasToPathPrefix.put(alias, path);
056            pathPrefixToAlias.put(path, alias);
057        }
058
059        Comparator<String> sortDescendingByLength = new Comparator<String>()
060        {
061            public int compare(String o1, String o2)
062            {
063                return o2.length() - o1.length();
064            }
065        };
066
067        sortedPathPrefixes = CollectionFactory.newList(aliasToPathPrefix.values());
068        Collections.sort(sortedPathPrefixes, sortDescendingByLength);
069    }
070
071    private String verify(String name, String input)
072    {
073
074        if (input.startsWith("/") || input.endsWith("/"))
075            throw new RuntimeException(String.format("Contribution of %s '%s' is invalid as it may not start with or end with a slash.",
076                    name, input));
077
078        return input;
079
080    }
081
082    public AssetAlias extractAssetAlias(Resource resource)
083    {
084        String resourcePath = resource.getPath();
085
086        for (String pathPrefix : sortedPathPrefixes)
087        {
088            if (resourcePath.startsWith(pathPrefix))
089            {
090                if (pathPrefix.length() == resourcePath.length())
091                {
092                    throw new IllegalArgumentException(String.format("Resource path '%s' is not valid as it is mapped as virtual folder '%s'.",
093                            resourcePath,
094                            pathPrefixToAlias.get(pathPrefix)));
095                }
096
097                // Prevent matching path prefix "foo" against "foobar" ... it must match against "foo/".
098                if (resourcePath.charAt(pathPrefix.length()) != '/')
099                {
100                    continue;
101                }
102
103                String virtualFolder = pathPrefixToAlias.get(pathPrefix);
104
105                // Don't want that slash seperating the folder from the rest of the path.
106                String path = resourcePath.substring(pathPrefix.length() + 1);
107
108                return new AssetAlias(virtualFolder, path);
109            }
110        }
111
112        // This is a minor misuse of the UnknownValueException but the exception reporting
113        // is too useful to pass up.
114
115        throw new UnknownValueException(
116                String.format(
117                        "Unable to create a client URL for classpath resource %s: The resource path was not within an aliased path.",
118                        resourcePath), new AvailableValues("Aliased paths", aliasToPathPrefix.values()));
119    }
120
121    public Map<String, String> getMappings()
122    {
123        return Collections.unmodifiableMap(aliasToPathPrefix);
124    }
125
126}