001    package net.sf.tacos.rest;
002    
003    import java.net.URL;
004    import java.net.MalformedURLException;
005    import java.io.File;
006    import java.io.IOException;
007    import java.util.Set;
008    import java.util.Map;
009    import javax.servlet.http.HttpServlet;
010    import javax.servlet.ServletContext;
011    
012    import org.apache.tapestry.INamespace;
013    import org.apache.tapestry.Tapestry;
014    import org.apache.tapestry.engine.ServiceEncoder;
015    import org.apache.tapestry.engine.ServiceEncoding;
016    import org.apache.tapestry.services.ServiceConstants;
017    import org.apache.tapestry.services.ApplicationInitializer;
018    import org.apache.commons.collections.FastHashMap;
019    
020    import org.scannotation.AnnotationDB;
021    
022    /**
023     * Rest-style encoder for pages.<p/> The urls generated and understood are of the form
024     * /view/pagename (so, you'll have to register a servlet mapping for /view in web.xml).
025     * <p/>
026     * If page classes have a {@link UriTemplate} annotation, upon initialization this service 
027     * will scan, gather and use those mappings for url generation (instead of the pagename).
028     */
029    public class RestPageEncoder implements ServiceEncoder, ApplicationInitializer {
030    
031        private String url;
032        private boolean caseSensitive;
033    
034        private Map<String, String> pathToPageMap;
035        private Map<String, String> pageToPathMap;
036    
037        public void encode(ServiceEncoding encoding) {
038            String service = encoding.getParameterValue(ServiceConstants.SERVICE);
039    
040            if (!service.equals(Tapestry.PAGE_SERVICE)) {
041                return;
042            }
043    
044            String pageName = encoding.getParameterValue(ServiceConstants.PAGE);
045    
046            // Only handle pages in the application namespace (not from a library).
047            if (pageName.indexOf(INamespace.SEPARATOR) >= 0) {
048                return;
049            }
050    
051            StringBuffer buffer = new StringBuffer(url);
052            buffer.append('/');
053            String pathOverride = pageToPathMap.get(pageName);
054            if (pathOverride != null) {
055                buffer.append(pathOverride);
056            } else {
057                buffer.append(pageName);
058            }
059    
060            encoding.setServletPath(buffer.toString());
061            encoding.setParameterValue(ServiceConstants.SERVICE, null);
062            encoding.setParameterValue(ServiceConstants.PAGE, null);
063        }
064    
065        public void decode(ServiceEncoding encoding) {
066            String servletPath = encoding.getServletPath();
067    
068            if (!servletPath.startsWith(url)) {
069                return;
070            }
071    
072            // handle only if there's path info
073            if (encoding.getPathInfo()==null) {
074                return;
075            }
076    
077            // Skip the slash and the dot.
078            String page = encoding.getPathInfo().substring(1);
079    
080            String pageOverride = caseSensitive ?
081                    pathToPageMap.get(page) : pathToPageMap.get(page.toLowerCase());
082            if (pageOverride != null) {
083                page = pageOverride;
084            }
085    
086            encoding.setParameterValue(ServiceConstants.SERVICE, Tapestry.PAGE_SERVICE);
087            encoding.setParameterValue(ServiceConstants.PAGE, page);
088        }
089    
090        public void setUrl(String url) {
091            this.url = url;
092        }
093    
094        public void setCaseSensitive(boolean caseSensitive) {
095            this.caseSensitive = caseSensitive;
096        }
097    
098        public void initialize(HttpServlet servlet) {
099            FastHashMap pageToPath = new FastHashMap();
100            FastHashMap pathToPage = new FastHashMap();
101    
102            URL webInfClasses = findWebInfClassesPath(servlet.getServletContext());
103    
104            scan(pageToPath, pathToPage, webInfClasses);
105    
106            storeMaps(pageToPath, pathToPage);
107        }
108    
109        private void scan(FastHashMap pageToPath, FastHashMap pathToPage, URL url) {
110            if (url == null)
111                return;
112    
113            AnnotationDB db = new AnnotationDB();
114            db.addIgnoredPackages("org");
115            try {
116                db.scanArchives(url);
117            } catch (IOException e) {
118                return;
119            }
120    
121            Set<String> simpleClasses = db.getAnnotationIndex().get(UriTemplate.class.getName());
122            if (simpleClasses == null)
123                return;
124    
125            for (String simpleClass : simpleClasses) {
126                UriTemplate uriTemplate;
127                try {
128                    uriTemplate = Class.forName(simpleClass).getAnnotation(UriTemplate.class);
129                } catch (ClassNotFoundException e) {
130                    continue;
131                }
132    
133                String path = uriTemplate.value();
134                if (path.startsWith("/")) {
135                    path = path.substring(1);
136                }
137                if (!caseSensitive) {
138                    path = path.toLowerCase();
139                }
140    
141                String page = toPage(simpleClass);
142                pathToPage.put(path, page);
143                pageToPath.put(page, path);
144    
145                // now process aliases
146                for (String aliasPath : uriTemplate.alias()) {
147                    pathToPage.put(aliasPath, page);
148                }
149    
150            }
151        }
152    
153        @SuppressWarnings("unchecked")
154        private void storeMaps(FastHashMap pageToPath, FastHashMap pathToPage) {
155            pageToPath.setFast(true);
156            pathToPage.setFast(true);
157            pageToPathMap = pageToPath;
158            pathToPageMap = pathToPage;
159        }
160    
161        /**
162         * Given the classname, returns the tapestry page name.
163         * TODO: assumes parent package is pages, better use tap. configuration of page packages
164         * @param className
165         * @return
166         */
167        private String toPage(String className) {
168            int pos = className.indexOf(".pages.");
169            return className.substring(pos + 7).replace('.', '/');
170        }
171    
172        private URL findWebInfClassesPath(ServletContext servletContext) {
173            String path = servletContext.getRealPath("/WEB-INF/classes");
174            if (path == null) return null;
175            File fp = new File(path);
176            if (!fp.exists()) return null;
177            try {
178                return fp.toURI().toURL();
179            }
180            catch (MalformedURLException e) {
181                throw new RuntimeException(e);
182            }
183        }
184    }