package knorxx.framework.generator.springadapter; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.io.CharStreams; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import knorxx.framework.generator.GenerationResult; import knorxx.framework.generator.GenerationRoots; import knorxx.framework.generator.GenerationUnit; import knorxx.framework.generator.application.KnorxxApplicationGenerator; import knorxx.framework.generator.application.PopulatableCache; import knorxx.framework.generator.library.LibraryDetector; import knorxx.framework.generator.library.LibraryUrls; import knorxx.framework.generator.single.JavaScriptResult; import static knorxx.framework.generator.springadapter.CacheRequestType.*; import static knorxx.framework.generator.springadapter.KnorxxGeneratorCacheConfig.GENERATOR_CACHE_NAME; import knorxx.framework.generator.web.KnorxxApplication; import knorxx.framework.generator.web.client.ErrorHandler; import knorxx.framework.generator.web.client.RpcService; import knorxx.framework.generator.web.client.WebPage; import knorxx.framework.generator.web.client.webpage.PageArranger; import knorxx.framework.generator.web.generator.CssResult; import knorxx.framework.generator.web.server.json.JsonHelper; import knorxx.framework.generator.web.server.rpc.ExceptionMarshaller; import knorxx.framework.generator.web.server.rpc.RpcCall; import knorxx.framework.generator.web.server.rpc.RpcCaller; import knorxx.framework.generator.web.server.rpc.RpcResult; import knorxx.framework.generator.web.server.rtti.RttiGenerationResult; import knorxx.framework.generator.web.server.rtti.UrlResolver; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; /** * * @author sj */ public abstract class KnorxxController implements ApplicationContextAware, PopulatableCache { public final static String FRAMEWORK_URL_PREFIX = "knorxx"; private final static String JSON_RPC_URL = "/" + FRAMEWORK_URL_PREFIX + "/rpc"; private final Class<?> applicationRootClass; private final Class<?> javaScriptGenerationRoot; private final Class<?> indexWebPageClass; private final Function<HttpServletRequest, GenerationRoots> generationRootsFunction; private final LibraryDetector libraryDetector; private final ExceptionMarshaller exceptionMarshaller; private final RpcCaller rpcCaller = new RpcCaller(); private final UrlResolver urlResolver; @Autowired KnorxxApplication knorxxApplication; @Autowired(required = false) Collection<RpcService> rpcServices = new ArrayList<>(); ApplicationContext applicationContext; @Autowired Collection<WebPage> webPages; @Autowired CacheManager cacheManager; @Autowired JsonHelper jsonHelper; @Autowired knorxx.framework.generator.web.client.JsonHelper javaScriptJsonHelper; @Autowired ErrorHandler javaScriptErrorHandler; KnorxxApplicationGenerator applicationGenerator; public KnorxxController(Class<?> applicationRootClass, Class<?> javaScriptGenerationRoot, Class<?> indexWebPageClass, Function<HttpServletRequest, GenerationRoots> generationRootsFunction, LibraryDetector libraryDetector, ExceptionMarshaller exceptionMarshaller) { this.applicationRootClass = applicationRootClass; this.indexWebPageClass = indexWebPageClass; this.javaScriptGenerationRoot = javaScriptGenerationRoot; this.generationRootsFunction = generationRootsFunction; this.libraryDetector = libraryDetector; this.exceptionMarshaller = exceptionMarshaller; this.urlResolver = new UrlResolver(FRAMEWORK_URL_PREFIX, javaScriptGenerationRoot.getPackage().getName()); } @PostConstruct private void init() { List<Class<?>> webPagesClasses = new ArrayList<>(); for(WebPage webPage : webPages) { webPagesClasses.add(webPage.getClass()); } applicationGenerator = new KnorxxApplicationGenerator(knorxxApplication, applicationRootClass.getPackage().getName(), javaScriptGenerationRoot.getPackage().getName(), webPagesClasses, libraryDetector, urlResolver, jsonHelper, javaScriptJsonHelper, javaScriptErrorHandler, JSON_RPC_URL); } @RequestMapping(value = "/") public String index() { return "redirect:/" + urlResolver.resolveWebPage(indexWebPageClass.getName()); } @RequestMapping(value = "/" + FRAMEWORK_URL_PREFIX + "/**/*.html") public ModelAndView webPage(HttpServletRequest request) throws IOException { Class<?> webPageClass = null; for(WebPage webPage : webPages) { if(urlResolver.resolveWebPage(webPage.getClass().getName()).equals(getUrl(request))) { webPageClass = webPage.getClass(); } } if(webPageClass != null) { return new ModelAndView("/presentation.jsp", applicationGenerator.generateWebPage( generationRootsFunction.apply(request), webPageClass, Optional.of(applicationContext.getBeansOfType(PageArranger.class).values()), request.getContextPath(), this)); } else { return null; } } @RequestMapping(value = {"/" + FRAMEWORK_URL_PREFIX + "/**/*.js", "/" + FRAMEWORK_URL_PREFIX + "/**/*.js.map", "/" + FRAMEWORK_URL_PREFIX + "/**/*.css", "/" + FRAMEWORK_URL_PREFIX + "/**/*.java"}) @ResponseBody public String responseFromCache(HttpServletRequest request) { return getResponseFromCache(request); } @RequestMapping(value = JSON_RPC_URL, method = RequestMethod.POST) @ResponseBody public String rpcCall(HttpServletRequest request) throws IOException { RpcCall rpcCall = new RpcCall(CharStreams.toString(new InputStreamReader(request.getInputStream(), "UTF-8"))); RpcResult rpcResult = rpcCaller.call(rpcCall, exceptionMarshaller, jsonHelper, new ArrayList<>(rpcServices), request); return jsonHelper.toJson(rpcResult); } public LibraryUrls populate(GenerationUnit unit, RttiGenerationResult rttiGenerationResult, UrlResolver urlResolver) { LibraryUrls cacheUrls = new LibraryUrls(); Cache generatorCache = cacheManager.getCache(GENERATOR_CACHE_NAME); // place RTTI URL last (otherwise the class definitions whould override the RTTI static members) for(GenerationResult result : Iterables.concat(unit.getGenerationResults(), Lists.newArrayList(rttiGenerationResult))) { if(result.getSingleResult() instanceof CssResult) { String cssUrl = urlResolver.resolveCssFile(result.getName()); cacheUrls.getCssUrls().add(cssUrl); generatorCache.put(cssUrl, result); } String javaScriptUrl = urlResolver.resolveJavaScriptFile(result.getName()); cacheUrls.getJavaScriptUrls().add(javaScriptUrl); generatorCache.put(javaScriptUrl, result); } return cacheUrls; } private String getUrl(HttpServletRequest request) { return request.getRequestURI().substring(request.getContextPath().length() + 1); } private String getResponseFromCache(HttpServletRequest request) { String url = getUrl(request); CacheRequestType requestType = JAVA_SCRIPT_OR_CSS; if (url.endsWith(".java")) { url = url.substring(0, url.length() - 4) + "js"; requestType = JAVA; } else if (url.endsWith(".js.map")) { url = url.substring(0, url.length() - 4); requestType = SOURCE_MAP; } GenerationResult result = (GenerationResult) cacheManager.getCache(GENERATOR_CACHE_NAME).get(url).get(); Optional<String> sourceMap = Optional.absent(); if (result.getSingleResult() instanceof JavaScriptResult) { sourceMap = ((JavaScriptResult) result.getSingleResult()).getSourceMap(); } if (requestType == SOURCE_MAP) { return sourceMap.get(); } else if (requestType == JAVA) { try { InputStream javaSourceInputStream = result.getJavaFile().getSourceInputStream().get(); return CharStreams.toString(new InputStreamReader(javaSourceInputStream, Charsets.UTF_8)); } catch(IOException ex) { throw new IllegalStateException("Can't resolve the Java source file referenced by the source mapping URL!", ex); } } else if (url.endsWith(".css")) { return ((CssResult) result.getSingleResult()).getCssSource(); } else { String response = result.getSingleResult().getSource(); if (sourceMap.isPresent()) { response += "\n//# sourceMappingURL=" + url.substring(url.lastIndexOf("/") + 1) + ".map"; } return response; } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }