Changeset 674


Ignore:
Timestamp:
2008-10-07 08:54:31 (5 years ago)
Author:
bruno
Message:

directory router:

  • implement reloading on changes.
  • implement double-extension idiom.
  • add feature to ignore certain files (.svn, CVS, ...)
  • and extended Routing2Test to cover all this
Location:
trunk
Files:
5 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/modules/kauri-routing/kauri-routing-impl/src/main/java/org/kauriproject/routing/impl/components/DirectoryRouter.java

    r672 r674  
    44import org.restlet.Router; 
    55import org.restlet.Context; 
     6import org.restlet.util.Template; 
    67import org.restlet.resource.Representation; 
    7 import org.restlet.data.Request; 
    8 import org.restlet.data.Response; 
    9 import org.restlet.data.MediaType; 
     8import org.restlet.data.*; 
    109import org.kauriproject.runtime.rapi.KauriModule; 
    1110import org.kauriproject.runtime.rapi.ModuleSource; 
    1211import org.kauriproject.runtime.rapi.ModuleSource.Resource; 
    1312import org.kauriproject.template.service.TemplateService; 
     13import org.kauriproject.routing.impl.routing.DisposableRoutingComponent; 
    1414import org.apache.commons.logging.LogFactory; 
    1515import org.apache.commons.logging.Log; 
     
    1818import java.util.regex.Pattern; 
    1919import java.util.regex.Matcher; 
     20import java.io.File; 
    2021 
    2122/** 
     
    2425 * are supposed to be executable templates. 
    2526 */ 
    26 public class DirectoryRouter extends Restlet { 
    27     private String path; 
    28     private Router router; 
     27public class DirectoryRouter extends Restlet implements DisposableRoutingComponent { 
     28    private String root; 
    2929    private ModuleSource moduleSource; 
    3030    private TemplateService templateService; 
    3131    private Context restletContext; 
    32     private Log log = LogFactory.getLog(getClass()); 
    33  
    34     private static Pattern DOUBLE_EXT = Pattern.compile("^(.*)\\.([^.]*)\\.([^.]+)$"); 
    35  
    36     public DirectoryRouter(String path, Context restletContext, KauriModule module, TemplateService templateService) { 
    37         this.path = path.replaceFirst("^/+", ""); 
     32    private ReloadableResource reloadableResource; 
     33    private List<Pattern> ignoreFilePatterns = new ArrayList<Pattern>(); 
     34    private final Log log = LogFactory.getLog(getClass()); 
     35 
     36    /** 
     37     * Explanation of this pattern: the purpose is to match file paths where the file 
     38     * name has a "double extension" (e.g. "/somedir/file.html.xml). The second-last 
     39     * extension is allowed to be empty (e.g. "/somedir/file..xml). If the file name 
     40     * starts with a dot, then it doesn't count as a double extension (e.g. "/somedir/.file.html"). 
     41     */ 
     42    private static Pattern DOUBLE_EXT = Pattern.compile("^(.*(?:[^/]+))\\.([^./]*)\\.([^./]+)$"); 
     43 
     44    public DirectoryRouter(String root, Context restletContext, KauriModule module, TemplateService templateService, List<String> ignoreFilePatterns) { 
     45        this.root = root.replaceFirst("^/+", ""); 
    3846        this.restletContext = restletContext; 
    3947        this.moduleSource = module.getSource(); 
    4048        this.templateService = templateService; 
     49        this.reloadableResource = new ReloadableResource(moduleSource, root, new DirectoryRouterBuilder(), new FAMListener(), "pages-restlet"); 
     50 
     51        for (String pattern : ignoreFilePatterns) { 
     52            pattern = pattern.trim(); 
     53            if (pattern.length() > 0) 
     54                this.ignoreFilePatterns.add(compileWildcardPattern(pattern)); 
     55        } 
     56    } 
     57 
     58    public void dispose() { 
     59        reloadableResource.disposeReloading(); 
    4160    } 
    4261 
    4362    @Override 
    4463    public void handle(Request request, Response response) { 
    45         build(); 
    46         router.handle(request, response); 
    47     } 
    48  
    49     private void build() { 
     64        ((Restlet)reloadableResource.build()).handle(request, response); 
     65    } 
     66 
     67    public Object build() { 
    5068        List<Page> pages = new ArrayList<Page>(); 
    51         scan(path, "", pages); 
     69        scan(root, "", pages); 
    5270 
    5371        // The "best" matching algorithm of Restlet's Router cannot meaningfully 
     
    6684 
    6785        Router router = new Router(); 
     86        router.setDefaultMatchingMode(Template.MODE_EQUALS); 
     87        router.setDefaultMatchQuery(false); 
    6888        for (Page page : pages) { 
    6989            if (log.isDebugEnabled()) { 
     
    7292            router.attach(page.uriPattern, new TemplateRestlet(templateService, restletContext, page.templatePath)); 
    7393        } 
    74         this.router = router; 
    75     } 
    76  
    77     private void scan(String parent, String mountPath, Collection<Page> pages) { 
    78         Resource res = moduleSource.getResource(parent); 
     94        return router; 
     95    } 
     96 
     97    private void scan(String filePath, String mountPath, Collection<Page> pages) { 
     98        Resource res = moduleSource.getResource(filePath); 
    7999 
    80100        Collection<String> children = res.getChildren(); 
    81101        for (String child : children) { 
    82             String childPath = parent + "/" + child; 
    83             Resource childRes = moduleSource.getResource(childPath); 
     102            if (ignore(child)) 
     103                continue; 
     104 
     105            String childFilePath = filePath + "/" + child; 
     106            String childMountPath = mountPath + "/" + child; 
     107            Resource childRes = moduleSource.getResource(childFilePath); 
    84108            if (childRes.isDirectory()) { 
    85                 scan(childPath, mountPath + "/" + child, pages); 
     109                scan(childFilePath, childMountPath, pages); 
    86110            } else { 
    87                 pages.add(createPage(mountPath + "/" + child)); 
    88             } 
    89         } 
    90     } 
    91  
    92     private static class TemplateRestlet extends Restlet { 
    93         private TemplateService templateService; 
    94         private Context restletContext; 
    95         private String templatePath; 
    96  
    97         private TemplateRestlet(TemplateService templateService, Context restletContext, String templatePath) { 
    98             this.templateService = templateService; 
    99             this.restletContext = restletContext; 
    100             this.templatePath = templatePath; 
    101         } 
    102  
    103         @Override 
    104         public void handle(Request request, Response response) { 
    105             // TODO only allow GET method ? 
    106             Map<String, Object> parameters = new HashMap<String, Object>(); 
    107             parameters.put("request", request); 
    108             Representation representation = templateService.getTemplateRepresentation(MediaType.TEXT_HTML, restletContext, request, templatePath, parameters); 
    109             response.setEntity(representation); 
    110         } 
     111                pages.add(createPage(childMountPath)); 
     112            } 
     113        } 
     114    } 
     115 
     116    /** 
     117     * Compiles a string containing '*' wildcards to a regular expression. 
     118     */ 
     119    private Pattern compileWildcardPattern(String pattern) { 
     120        StringBuilder result = new StringBuilder(); 
     121        StringBuilder buffer = new StringBuilder(); 
     122 
     123        char c; 
     124        for (int i = 0; i < pattern.length(); i++) { 
     125            c = pattern.charAt(i); 
     126            if (c == '*') { 
     127                if (buffer.length() > 0) { 
     128                    result.append(Pattern.quote(buffer.toString())); 
     129                    buffer.setLength(0); 
     130                } 
     131                result.append(".*"); 
     132            } else { 
     133                buffer.append(c); 
     134            } 
     135        } 
     136 
     137        if (buffer.length() > 0) { 
     138            result.append(Pattern.quote(buffer.toString())); 
     139        } 
     140 
     141        return Pattern.compile(result.toString()); 
     142    } 
     143 
     144    private boolean ignore(String name) { 
     145        for (Pattern pattern : ignoreFilePatterns) { 
     146            if (pattern.matcher(name).matches()) 
     147                return true; 
     148        } 
     149        return false; 
    111150    } 
    112151 
    113152    private Page createPage(String filePath) { 
    114         String templatePath = "kms:/" + this.path + filePath; 
     153        String templatePath = "kms:/" + this.root + filePath; 
    115154        Matcher matcher = DOUBLE_EXT.matcher(filePath); 
    116155        if (matcher.matches()) { 
     156            // Double extension feature: the purpose is to allow a file to 
     157            // have a different extension on the file system and in the public 
     158            // URI path. It also allows to have no extension in the URI path, 
     159            // while having an extension on the file system. 
     160            // For example: 
     161            //    File path                 URI path 
     162            //    /somedir/file.html.xml    /somedir/file.html 
     163            //    /somedir/file..xml        /somedir/file 
     164            // The double extension rule doesn't count if the file starts with 
     165            // a dot. 
    117166            String base = matcher.group(1); 
    118167            String publicExt = matcher.group(2); 
     
    135184        } 
    136185    } 
     186 
     187    private static class TemplateRestlet extends Restlet { 
     188        private TemplateService templateService; 
     189        private Context restletContext; 
     190        private String templatePath; 
     191 
     192        private TemplateRestlet(TemplateService templateService, Context restletContext, String templatePath) { 
     193            this.templateService = templateService; 
     194            this.restletContext = restletContext; 
     195            this.templatePath = templatePath; 
     196        } 
     197 
     198        @Override 
     199        public void handle(Request request, Response response) { 
     200            if (request.getMethod().equals(Method.GET)) { 
     201                Map<String, Object> parameters = new HashMap<String, Object>(); 
     202                parameters.put("request", request); 
     203                Representation representation = templateService.getTemplateRepresentation(MediaType.TEXT_HTML, restletContext, request, templatePath, parameters); 
     204                response.setEntity(representation); 
     205            } else { 
     206                response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); 
     207            } 
     208        } 
     209    } 
     210 
     211    private class DirectoryRouterBuilder implements ReloadableResource.ResourceBuilder { 
     212        public Object build() { 
     213            return DirectoryRouter.this.build(); 
     214        } 
     215    } 
     216 
     217    private class FAMListener implements ReloadableResource.FAMListener { 
     218        public boolean onFileCreate(File file) { 
     219            return true; 
     220        } 
     221 
     222        public boolean onFileChange(File file) { 
     223            return true; 
     224        } 
     225 
     226        public boolean onFileDelete(File file) { 
     227            return true; 
     228        } 
     229 
     230        public boolean onDirectoryCreate(File file) { 
     231            return true; 
     232        } 
     233 
     234        public boolean onDirectoryChange(File file) { 
     235            return true; 
     236        } 
     237 
     238        public boolean onDirectoryDelete(File file) { 
     239            return true; 
     240        } 
     241    } 
    137242} 
  • trunk/modules/kauri-routing/kauri-routing-impl/src/main/java/org/kauriproject/routing/impl/groovybuild/GroovyKauriRouterBuilder.java

    r671 r674  
    4545        restletBuilder.registerFactory(JaxRsGroovyScriptsFactory.METHOD_NAME, new JaxRsGroovyScriptsFactory()); 
    4646        restletBuilder.registerFactory(ReadFactory.METHOD_NAME, new ReadFactory(context)); 
    47         restletBuilder.registerFactory(DirectoryRouterFactory.METHOD_NAME, new DirectoryRouterFactory(context, module, routingConfig.getTemplateService())); 
     47        restletBuilder.registerFactory(DirectoryRouterFactory.METHOD_NAME, new DirectoryRouterFactory(context, module, routingConfig.getTemplateService(), buildContext)); 
    4848        restletBuilder.registerFactory(TemplateFactory.METHOD_NAME, new TemplateFactory(context, routingConfig.getTemplateService())); 
    4949        restletBuilder.registerFactory(ModeFactory.METHOD_NAME, new ModeFactory(module)); 
  • trunk/modules/kauri-routing/kauri-routing-impl/src/main/java/org/kauriproject/routing/impl/groovybuild/factory/DirectoryRouterFactory.java

    r651 r674  
    44import org.kauriproject.routing.impl.components.DirectoryRouter; 
    55import org.kauriproject.routing.impl.RoutingConfigurationException; 
     6import org.kauriproject.routing.impl.routing.RouterBuildContext; 
    67import org.kauriproject.runtime.rapi.KauriModule; 
    78import org.kauriproject.template.service.TemplateService; 
     
    910 
    1011import java.util.Map; 
     12import java.util.List; 
     13import java.util.Arrays; 
     14import java.util.ArrayList; 
    1115 
    1216public class DirectoryRouterFactory extends RestletFactory { 
    1317    public static final String METHOD_NAME = "directoryRouter"; 
    1418    private static final String ROOT = "root"; 
     19    private static final String IGNORE = "ignore"; 
    1520 
    1621    private Context context; 
    1722    private KauriModule kauriModule; 
    1823    private TemplateService templateService; 
     24    private RouterBuildContext buildContext; 
     25    private static List<String> BUILD_IN_IGNORES; 
     26    static { 
     27        BUILD_IN_IGNORES = new ArrayList<String>(); 
     28        BUILD_IN_IGNORES.add(".svn"); 
     29        BUILD_IN_IGNORES.add("CVS"); 
     30        BUILD_IN_IGNORES.add(".DS_Store"); 
     31        BUILD_IN_IGNORES.add("SCCS"); 
     32        BUILD_IN_IGNORES.add("RCS"); 
     33        BUILD_IN_IGNORES.add("rcs"); 
     34        BUILD_IN_IGNORES.add(".sbas"); 
     35        BUILD_IN_IGNORES.add(".IJI.*"); 
     36        BUILD_IN_IGNORES.add("vssver.scc"); 
     37        BUILD_IN_IGNORES.add("vssver2.scc"); 
     38    } 
    1939 
    20     public DirectoryRouterFactory(Context context, KauriModule kauriModule, TemplateService templateService) { 
     40    public DirectoryRouterFactory(Context context, KauriModule kauriModule, TemplateService templateService, 
     41            RouterBuildContext buildContext) { 
    2142        super(); 
    2243        this.context = context; 
    2344        this.kauriModule = kauriModule; 
    2445        this.templateService = templateService; 
    25         addFilter(ROOT); 
     46        this.buildContext = buildContext; 
     47        addFilter(ROOT).addFilter(IGNORE); 
    2648    } 
    2749 
     
    3153            throw new RoutingConfigurationException(METHOD_NAME + " is used in the routing configuration, but no template service implementation is provided."); 
    3254        String rootPath = (String)builder.getContext().get(ROOT); 
    33         DirectoryRouter dirRouter = new DirectoryRouter(rootPath, context, kauriModule, templateService); 
     55        String ignoreSpec = (String)builder.getContext().get(IGNORE); 
     56        List<String> ignores = ignoreSpec != null ? Arrays.asList(ignoreSpec.split(" ")) : BUILD_IN_IGNORES; 
     57        DirectoryRouter dirRouter = new DirectoryRouter(rootPath, context, kauriModule, templateService, ignores); 
     58        buildContext.addDisposable(dirRouter); 
    3459        return dirRouter; 
    3560    } 
  • trunk/modules/kauri-routing/kauri-routing-impl/src/test/java/org/kauriproject/routing/test/Routing2Test.java

    r671 r674  
    1010import java.util.Collections; 
    1111 
     12/** 
     13 * Test case for directory router, sequence router, and runtime mode switch. 
     14 */ 
    1215public class Routing2Test extends AbstractRuntimeTest { 
    1316    private KauriRuntime runtime; 
     
    5558            assertEquals(200, response.getStatus().getCode()); 
    5659            assertTrue(response.getEntity().getText().indexOf("Always here") > -1); 
     60 
     61            response = get("/ignorethis"); 
     62            assertEquals(404, response.getStatus().getCode()); 
     63 
     64            response = get("/.startswithdot.html"); 
     65            assertEquals(200, response.getStatus().getCode()); 
     66 
     67            response = get("/differentextension.html"); 
     68            assertEquals(200, response.getStatus().getCode()); 
     69 
     70            response = get("/differentextension.html.xml"); 
     71            assertEquals(404, response.getStatus().getCode()); 
     72 
     73            response = get("/noextension"); 
     74            assertEquals(200, response.getStatus().getCode()); 
     75 
     76            response = get("/noextension."); 
     77            assertEquals(404, response.getStatus().getCode()); 
     78 
     79            response = get("/noextension..html"); 
     80            assertEquals(404, response.getStatus().getCode()); 
    5781        } finally { 
    5882            runtime.stop(); 
     
    7397            assertEquals(200, response.getStatus().getCode()); 
    7498            assertTrue(response.getEntity().getText().indexOf("Always here") > -1); 
     99 
     100            // for production, we didn't set the ignore filter, so the file should be accessible 
     101            response = get("/ignorethis"); 
     102            assertEquals(200, response.getStatus().getCode()); 
    75103        } finally { 
    76104            runtime.stop(); 
  • trunk/modules/kauri-routing/kauri-routing-impl/src/test/modulesrc/org/kauriproject/routing/test/testmodules/routing2/KAURI-INF/router.groovy

    r671 r674  
    11builder.router { 
    22    mode(uri: "", continueIfNotFound: true, when: "prototype") { 
    3         directoryRouter(root: "/site/pages") 
     3        directoryRouter(root: "/site/pages", ignore: ".svn CVS ignorethis") 
    44    } 
    55 
  • trunk/universe/kauri-template/src/main/java/org/kauriproject/template/DefaultTemplateBuilder.java

    r656 r674  
    112112            xmlReader.parse(new InputSource(in)); 
    113113            complete = CompiledTemplate.COMPLETE; 
    114         } catch (SAXException saxex) { 
    115             log.error(saxex); 
    116         } catch (IOException ioex) { 
    117             log.error(ioex); 
     114        } catch (Exception ex) { 
     115            // TODO 
     116            throw new RuntimeException("Error parsing template file " + source.getReference(), ex); 
    118117        } finally { 
    119118            try { 
Note: See TracChangeset for help on using the changeset viewer.