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 }