001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.assets; 019 020import static io.stallion.utils.Literals.*; 021import static io.stallion.Context.*; 022 023import java.io.File; 024import java.io.IOException; 025import java.io.UnsupportedEncodingException; 026import java.net.URISyntaxException; 027import java.net.URL; 028import java.net.URLEncoder; 029import java.text.MessageFormat; 030import java.util.List; 031import java.util.Map; 032 033import io.stallion.Context; 034import io.stallion.assetBundling.*; 035import io.stallion.exceptions.UsageException; 036import io.stallion.plugins.PluginRegistry; 037import io.stallion.services.Log; 038import io.stallion.settings.Settings; 039import org.apache.commons.codec.digest.DigestUtils; 040import org.apache.commons.io.IOUtils; 041import org.apache.http.client.utils.URLEncodedUtils; 042 043 044public class ResourceAssetBundleRenderer { 045 046 private String path; 047 private String plugin; 048 private String extension; 049 private Class resourceClass; 050 private URL resourceUrl; 051 052 public ResourceAssetBundleRenderer(String plugin, String path) { 053 this.plugin = or(plugin, "stallion"); 054 if (path.endsWith(".bundle")) { 055 throw new UsageException("Invalid bundle path: " + path + ". The path must end with the extension for the type of content from the bundle that you want to render - .css, .js, or .head.js"); 056 } 057 path = AssetsController.ensureSafeAssetsPath(path); 058 int i = path.indexOf(".bundle.") + 7; 059 if (i == -1) { 060 throw new UsageException("The path - " + path + " - is not a valid bundle name. Expected to end with .bundle.js, .bundle.head.js, or .bundle.css"); 061 } 062 this.extension = path.substring(i); 063 this.path = path.substring(0, i); 064 this.resourceClass = ResourceAssetBundleRenderer.class; 065 if (!empty(plugin) && !plugin.equals("stallion")) { 066 this.resourceClass = PluginRegistry.instance().getJavaPluginByName().get(plugin).getClass(); 067 } 068 resourceUrl = resourceClass.getResource(this.path); 069 if (resourceUrl == null) { 070 throw new UsageException("The bundle you are trying to render - " + this.path + " - does not exist in the plugin with class " + resourceClass.getCanonicalName()); 071 } 072 073 } 074 075 public String renderDebugHtml() { 076 if (!resourceUrl.getProtocol().equals("file")) { 077 return renderProductionHtml(); 078 } 079 String bundleFilePath = null; 080 try { 081 bundleFilePath = resourceUrl.toURI().getPath(); 082 } catch (URISyntaxException e) { 083 throw new RuntimeException(e); 084 } 085 File debugVersion = new File(bundleFilePath.replace("/target/classes/", "/src/main/resources/")); 086 if (debugVersion.exists()) { 087 bundleFilePath = debugVersion.getAbsolutePath(); 088 } 089 AssetBundle bundle = io.stallion.assetBundling.BundleRegistry.instance().getByPath(bundleFilePath); 090 bundle.hydrateFilesIfNeeded(true); 091 StringBuilder builder = new StringBuilder(); 092 for (AssetFile af: bundle.getFiles()) { 093 af.hydrateIfNeeded(true); 094 String tag = "<script src=\"{0}\" type=\"text/javascript\"></script>"; 095 if (".head.js".equals(extension)) { 096 if (empty(af.getHeadJavaScript())) { 097 continue; 098 } 099 } else if (".css".equals(extension)) { 100 if (empty(af.getCss())) { 101 continue; 102 } 103 tag = "<link rel=\"stylesheet\" href=\"{0}\">"; 104 } else if (".js".equals(extension)) { 105 if (empty(af.getJavaScript())) { 106 continue; 107 } 108 } 109 String url; 110 try { 111 String relativePath = af.getRelativePath(); 112 if (!relativePath.startsWith("/")) { 113 relativePath = "/" + relativePath; 114 } 115 url = Settings.instance().getCdnUrl() + "/st-resource/" + plugin + relativePath + extension + "?bundlePath=" + URLEncoder.encode(this.path, "utf-8") + "&ts=" + af.getHydratedAt(); 116 } catch (UnsupportedEncodingException e) { 117 throw new RuntimeException(e); 118 } 119 builder.append(" " + MessageFormat.format(tag, url) + "\n"); 120 121 } 122 return builder.toString(); 123 124 } 125 126 public String renderProductionHtml() { 127 String content = renderProductionContent(); 128 String hash = DigestUtils.md5Hex(content); 129 String tag = "<script src=\"{0}\" type=\"text/javascript\"></script>"; 130 String url = Settings.instance().getCdnUrl() + "/st-resource/" + plugin + path + extension + "?isFullResourceBundle=true&hash=" + hash; 131 if (".css".equals(extension)) { 132 tag = "<link rel=\"stylesheet\" href=\"{0}\">"; 133 } 134 return MessageFormat.format(tag, url); 135 } 136 137 138 139 public String renderProductionContent() { 140 URL url = resourceClass.getResource(path + ".min" + extension); 141 if (url == null) { 142 url = resourceClass.getResource(path + extension); 143 } 144 if (url == null) { 145 throw new UsageException("Could not find compiled bundle for path: " + path); 146 } 147 if (extension.equals(".js") || extension.equals(".head.js")) { 148 Context.response().addHeader("X-Sourcemap", "/st-resource/" + this.plugin + path + ".min" + extension + ".map"); 149 } 150 try { 151 return IOUtils.toString(url, "UTF-8"); 152 } catch (IOException e) { 153 throw new RuntimeException(e); 154 } 155 156 } 157}