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 }