/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.seam.wiki.core.ui;
import static org.jboss.seam.ScopeType.APPLICATION;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;
import org.jboss.seam.servlet.ContextualHttpServletRequest;
import org.jboss.seam.util.Resources;
import org.jboss.seam.web.ConditionalAbstractResource;
import org.jboss.seam.wiki.core.plugin.PluginRegistry;
import org.jboss.seam.wiki.core.plugin.metamodel.Plugin;
import org.jboss.seam.wiki.core.plugin.metamodel.PluginModule;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Serves files from the classpath, from a Plugin theme package.
* <p>
* This means that any web request can get any file out of that package. So don't put
* anything into a plugins theme directory that you don't want people to GET.
* </p>
* <p>
* It is primarily used for serving up plugin CSS and image files. It can also interpolate
* EL expressions in certain resources, configured with <tt>interpolatedResourcesExtensions</tt>.
* The default is to parse resources with <tt>css</tt> extension. A <tt>PluginModule</tt> instance
* is always available as variable <tt>currentPluginModule</tt>, which allows direct access to metadata
* and path information such as <tt>imagePath</tt> and <tt>styleSheetPath</tt>.
* </p>
*
* @author Christian Bauer
*/
@Scope(APPLICATION)
@Name("wikiPluginThemeResource")
@BypassInterceptors
public class WikiPluginThemeResource extends ConditionalAbstractResource {
private Log log = Logging.getLog(WikiPluginThemeResource.class);
private static final Pattern EL_PATTERN = Pattern.compile("#" + Pattern.quote("{") + "(.*)" + Pattern.quote("}"));
// Resources URIs end with /<pluginKey/<pluginModuleKey>/<themeResourceName>.<themeResourceExtension>
public static Pattern PLUGIN_RESOURCE_PATTERN =
Pattern.compile("^/(" + Plugin.KEY_PATTERN + ")/(" + Plugin.KEY_PATTERN + ")/(.+?)\\.([a-z]+)$");
// Resources that are interpolated, i.e. which are text files that contain EL expressions
private String[] interpolatedResourcesExtensions = new String[]{"css"};
private Map<String, String> mimeTypesExtensions = new HashMap() {{
put("css", "text/css");
put("txt", "text/plain");
put("js", "text/javascript");
put("png", "image/png");
put("jpg", "image/jpg");
put("jpeg", "image/jpeg");
put("gif", "image/gif");
put("swf", "application/x-shockwave-flash");
put("flv", "video/x-flv");
}};
@Override
public String getResourcePath() {
return Plugin.REGISTER_SEAM_RESOURCE_THEME;
}
@Override
public void getResource(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
// Wrap this, we need an ApplicationContext
new ContextualHttpServletRequest(request) {
@Override
public void process() throws IOException {
doWork(request, response);
}
}.run();
}
public String[] getInterpolatedResourcesExtensions() {
return interpolatedResourcesExtensions;
}
public void setInterpolatedResourcesExtensions(String[] interpolatedResourcesExtensions) {
this.interpolatedResourcesExtensions = interpolatedResourcesExtensions;
}
public void doWork(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String pathInfo = request.getPathInfo().substring(getResourcePath().length());
String pluginKey = null;
String pluginModuleKey = null;
String themeResourceName = null;
String themeResourceExtension = null;
Matcher matcher = PLUGIN_RESOURCE_PATTERN.matcher(pathInfo);
if (matcher.find()) {
pluginKey = matcher.group(1);
pluginModuleKey = matcher.group(2);
themeResourceName = matcher.group(3);
themeResourceExtension = matcher.group(4);
log.debug("request for resource,"
+ " plugin key '" + pluginKey + "'"
+ " plugin module key '" + pluginModuleKey + "'"
+ " theme resource name '" + themeResourceName + "'"
+ " theme resource ext '" + themeResourceExtension + "'"
);
}
if (pluginKey == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Plugin key not found");
return;
}
if (pluginModuleKey == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Plugin module key not found");
return;
}
if (themeResourceName == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Resource name not found");
return;
}
PluginRegistry registry = PluginRegistry.instance();
Plugin plugin = registry.getPlugin(pluginKey);
if (plugin == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Plugin not found in registry: " + pluginKey);
return;
}
PluginModule pluginModule = plugin.getModuleByKey(pluginModuleKey);
if (pluginModule == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Plugin module not found: " + pluginKey+"."+pluginModuleKey);
return;
}
org.jboss.seam.contexts.Contexts.getEventContext().set("currentPluginModule", pluginModule);
String resourcePath = plugin.getPackageThemePath() + "/" + themeResourceName + "." + themeResourceExtension;
InputStream in = Resources.getResourceAsStream(resourcePath, getServletContext());
if (in != null) {
boolean isInterpolated = false;
for (String interpolatedResourcesExtension : interpolatedResourcesExtensions) {
if (interpolatedResourcesExtension.equals(themeResourceExtension)) {
isInterpolated = true;
break;
}
}
String contentType = mimeTypesExtensions.get(themeResourceExtension);
if (isInterpolated) {
if (contentType == null) {
contentType = "text/plain";
}
response.setContentType(contentType);
CharSequence textFile = readFile(in);
textFile = parseEL(textFile);
String entityTag = createEntityTag(textFile.toString(), false);
Long lastModified = getLastModifiedTimestamp(resourcePath);
if (!sendConditional(request, response, entityTag, lastModified)) {
log.debug("serving interpolated resource: " + resourcePath);
OutputStreamWriter outStream = new OutputStreamWriter(selectOutputStream(request, response));
outStream.write(textFile.toString());
outStream.close();
}
} else {
log.debug("serving resource: " + resourcePath);
if (contentType == null) {
contentType = "application/octet-stream";
}
response.setContentType(contentType);
// We can't produce an entity tag because we stream the bytes through (or not, through the gzip wrapper)
if (!sendConditional(request, response, null, getLastModifiedTimestamp(resourcePath))) {
OutputStream outStream = selectOutputStream(request, response);
byte[] buffer = new byte[1024];
int read = in.read(buffer);
while (read != -1) {
outStream.write(buffer, 0, read);
read = in.read(buffer);
}
outStream.close();
}
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND, resourcePath);
}
}
private CharSequence readFile(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder css = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
css.append(line);
css.append("\n");
}
inputStream.close();
return css;
}
// Resolve any EL value binding expression present in text resource
// This should be Interpolator.interpolate, but it seems to break on CSS
private CharSequence parseEL(CharSequence string) {
StringBuffer parsed = new StringBuffer(string.length());
Matcher matcher =
EL_PATTERN.matcher(string);
while (matcher.find()) {
String result = Expressions.instance().createValueExpression(
"#{" + matcher.group(1) + "}", String.class
).getValue();
if (result != null) {
matcher.appendReplacement(parsed, result);
} else {
matcher.appendReplacement(parsed, "");
}
}
matcher.appendTail(parsed);
return parsed;
}
}