/* Copyright (20072012) Schibsted ASA
* This file is part of Possom.
*
* Possom is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Possom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Possom. If not, see <http://www.gnu.org/licenses/>.
*/
package no.sesat.search.view.velocity;
import java.io.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import no.sesat.Interpreter;
import org.apache.log4j.Logger;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
*
* Load templates and adds debuginformation if VELOCITY_DEBUG is set to true.
*
* @see URLResourceLoader
*
*
*
*
*/
public class URLVelocityTemplateLoader extends URLResourceLoader {
private static final Logger LOG = Logger.getLogger(URLVelocityTemplateLoader.class);
private static final String DIV_TAG = "div";
private static final String STYLE_ATTRIB = "style";
private static final String STYLE_BORDER = "margin:3px;border:1px solid #C0C0C0";
private static final String STYLE_HEADING="text-decoration:underline;font-size:10px";
private static final String ONMOUSEOVER = "this.style.border='1px solid #C0C0C0';this.style.margin='4px'";
private static final String ONMOUSEOUT = "this.style.border='none'";
private static final String VELOCITY_DEBUG = "VELOCITY_DEBUG";
private static final String VELOCITY_DEBUG_ON = "VELOCITY_DEBUG_ON";
private static final String VELOCITY_DEBUG_STYLE = "VELOCITY_DEBUG_STYLE";
private static final String VELOCITY_DEVELOP_BASEDIR = "VELOCITY_DEVELOP_BASEDIR";
/**
* getResourceStream() loads resource from url. Then add border around the
* template so its easy to see wich templates are loaded.
* @throws org.apache.velocity.exception.ResourceNotFoundException
*/
@Override
public InputStream getResourceStream(final String url) throws ResourceNotFoundException {
// Enable/disable velocity debug
final boolean velocityDebug = Boolean.getBoolean(VELOCITY_DEBUG);
// Activate debug (Show borders/debuginfo)
final boolean velocityDebugOn = Boolean.getBoolean(VELOCITY_DEBUG_ON);
// Onmouseover style
final boolean styleOnmouseover ="onmouseover".equals(System.getProperty(VELOCITY_DEBUG_STYLE));
// Silent style
final boolean styleSilent ="silent".equals(System.getProperty(VELOCITY_DEBUG_STYLE));
// Indicates if we found file local.(Can be edited)
boolean foundLocal = false;
if(velocityDebug) {
final String templatesDir = System.getProperty(VELOCITY_DEVELOP_BASEDIR);
// Get the file equivalece of the URL by removing the host as well as the web application context path.
final String filePath = url.replaceAll("http://(.*?)/[^/]+/", "/").replace("localhost/", "");
final File file = getFile(templatesDir, filePath);
foundLocal = file.exists();
final InputStream stream = file.exists() ? getStream(file) : super.getResourceStream(url);
if(velocityDebugOn && -1 == url.indexOf("rss")){
StringBuilder content = this.readTemplateFrom(stream);
// Create html
final StringBuilder template= new StringBuilder();
template.append("\n");
template.append(content);
template.append("\n");
final StringWriter writer = new StringWriter();
final Document doc = createDocument();
final Element div = doc.createElement(DIV_TAG);
if(styleOnmouseover) {
div.setAttribute("onmouseover", ONMOUSEOVER);
div.setAttribute("onmouseout", ONMOUSEOUT);
}else if(styleSilent){
// Just print title as popup.
}else {
final Element divHeader = doc.createElement(DIV_TAG);
divHeader.setAttribute(STYLE_ATTRIB, STYLE_HEADING);
divHeader.appendChild(doc.createTextNode(filePath));
div.appendChild(divHeader);
div.setAttribute("style", STYLE_BORDER);
}
div.setAttribute("title", file.getName() + (foundLocal ? "(Editable)" : "(Not editable)"));
div.appendChild(doc.createCDATASection(template.toString()));
doc.appendChild(div);
internalWriteDocument(doc, writer);
final String result = writer.getBuffer().toString()
.replace("<![CDATA[", "")
.replace("]]>", "");
return new ByteArrayInputStream(result.getBytes());
}else{
// If debug is not currently activated OR If rss, means the output is xml.
return stream;
}
}
return super.getResourceStream(url);
}
/**
* Read template utf8.
* @param is to read from
* @return template as StringBuilder object
*/
private StringBuilder readTemplateFrom(final InputStream is) {
StringBuilder builder = new StringBuilder();
try {
InputStreamReader isr = new InputStreamReader(is, "UTF8");
Reader in = new BufferedReader(isr);
int ch;
while ((ch = in.read()) > -1) {
builder.append((char)ch);
}
in.close();
} catch (IOException e) {
LOG.error(e);
}
return builder;
}
/**
* Create file object
*/
private File getFile(final String templatesDir, final String filePath) {
File result = null;
if(null == templatesDir) {
result = new File("null" + filePath);
}else{
for(String p : templatesDir.split(",")) {
final File file = new File(p + filePath);
if(file.exists()) {
result = file;
break;
}
}
}
return null == result ? new File(filePath) : result;
}
/*
* Get stream from file of or url.
*/
private InputStream getStream(final File file) {
if (file.exists()) {
try {
FileInputStream fileStream = new FileInputStream(file);
return fileStream;
} catch (FileNotFoundException ignore) {
throw new IllegalStateException("File exist but filenotfoundexception thrown: " + ignore);
}
} else {
throw new IllegalArgumentException("File does not exist");
}
}
// -- Write the document to the writer
private void internalWriteDocument(final Document d, final Writer w) {
final DOMSource source = new DOMSource(d);
final StreamResult result = new StreamResult(w);
final TransformerFactory factory = TransformerFactory.newInstance();
try {
final Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(source, result);
} catch (TransformerConfigurationException e) {
throw new RuntimeException("Xml Parser: " + e);
} catch (TransformerException ignore) {
LOG.debug("Ingoring the following ", ignore);
}
}
// -- Create a DOM document
private Document createDocument() {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
try {
return docFactory.newDocumentBuilder().newDocument();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
/**
* Add some debug function to the interpreter.
*/
static {
Interpreter.addFunction("velocity-debug", new Interpreter.Function() {
public String execute(Interpreter.Context ctx) {
if (ctx.length() == 1) {
System.setProperty(VELOCITY_DEBUG_ON, ctx.getArgument(0));
String style = ctx.getKeywordArgument("style");
if (style != null) {
if (style.toUpperCase().startsWith("ON")) {
System.setProperty(VELOCITY_DEBUG_STYLE, "onmouseover");
}
else if (style.toUpperCase().startsWith("SI")) {
System.setProperty(VELOCITY_DEBUG_STYLE, "silent");
}
else {
System.setProperty(VELOCITY_DEBUG_STYLE, "default");
}
}
}
String res = VELOCITY_DEBUG + " = " + Boolean.parseBoolean(System.getProperty(VELOCITY_DEBUG)) + " (must be set before starting up)\n";
res += VELOCITY_DEBUG_ON + " = " + Boolean.parseBoolean(System.getProperty(VELOCITY_DEBUG_ON)) + "\n";
res += VELOCITY_DEBUG_STYLE + " = " + System.getProperty(VELOCITY_DEBUG_STYLE) + "\n";
res += VELOCITY_DEVELOP_BASEDIR + " = " + System.getProperty(VELOCITY_DEVELOP_BASEDIR) + "\n";
return res;
}
@Override
protected String describe() {
return "Change or view velocity debug options. 'velocity-debug [true|false] [style=silent|onmouseover|default]'";
}
});
}
}