001// Copyright 2012, 2013 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.javascript; 016 017import org.apache.tapestry5.internal.services.AssetDispatcher; 018import org.apache.tapestry5.internal.services.ResourceStreamer; 019import org.apache.tapestry5.ioc.IOOperation; 020import org.apache.tapestry5.ioc.OperationTracker; 021import org.apache.tapestry5.ioc.Resource; 022import org.apache.tapestry5.services.Dispatcher; 023import org.apache.tapestry5.services.PathConstructor; 024import org.apache.tapestry5.services.Request; 025import org.apache.tapestry5.services.Response; 026import org.apache.tapestry5.services.javascript.ModuleManager; 027 028import java.io.IOException; 029import java.util.EnumSet; 030import java.util.Set; 031 032/** 033 * Handler contributed to {@link AssetDispatcher} with key "modules". It interprets the extra path as a module name, 034 * and searches for the corresponding JavaScript module. Unlike normal assets, modules do not include any kind of checksum 035 * in the URL, and do not set a far-future expires header. 036 * 037 * @see ModuleManager 038 */ 039public class ModuleDispatcher implements Dispatcher 040{ 041 private final ModuleManager moduleManager; 042 043 private final ResourceStreamer streamer; 044 045 private final OperationTracker tracker; 046 047 private final String requestPrefix; 048 049 private final boolean compress; 050 051 private final Set<ResourceStreamer.Options> omitExpiration = EnumSet.of(ResourceStreamer.Options.OMIT_EXPIRATION); 052 053 public ModuleDispatcher(ModuleManager moduleManager, 054 ResourceStreamer streamer, 055 OperationTracker tracker, 056 PathConstructor pathConstructor, 057 String prefix, 058 boolean compress) 059 { 060 this.moduleManager = moduleManager; 061 this.streamer = streamer; 062 this.tracker = tracker; 063 this.compress = compress; 064 065 requestPrefix = pathConstructor.constructDispatchPath(compress ? prefix + ".gz" : prefix) + "/"; 066 } 067 068 public boolean dispatch(Request request, Response response) throws IOException 069 { 070 String path = request.getPath(); 071 072 return path.startsWith(requestPrefix) && 073 handleModuleRequest(path.substring(requestPrefix.length())); 074 075 } 076 077 private boolean handleModuleRequest(String extraPath) throws IOException 078 { 079 // Ensure request ends with '.js'. That's the extension tacked on by RequireJS because it expects there 080 // to be a hierarchy of static JavaScript files here. In reality, we may be cross-compiling CoffeeScript to 081 // JavaScript, or generating modules on-the-fly, or exposing arbitrary Resources from somewhere on the classpath 082 // as a module. 083 084 int dotx = extraPath.lastIndexOf('.'); 085 086 if (dotx < 0) 087 { 088 return false; 089 } 090 091 if (!extraPath.substring(dotx + 1).equals("js")) 092 { 093 return false; 094 } 095 096 final String moduleName = extraPath.substring(0, dotx); 097 098 return tracker.perform(String.format("Streaming %s %s", 099 compress ? "compressed module" : "module", 100 moduleName), new IOOperation<Boolean>() 101 { 102 public Boolean perform() throws IOException 103 { 104 Resource resource = moduleManager.findResourceForModule(moduleName); 105 106 if (resource != null) 107 { 108 // Slightly hacky way of informing the streamer whether to supply the 109 // compressed or default stream. May need to iterate the API on this a bit. 110 return streamer.streamResource(resource, compress ? "z" : "", omitExpiration); 111 } 112 113 return false; 114 } 115 }); 116 } 117}