/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.view.internal.servlet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.eclipse.skalli.services.FilterMode;
import org.eclipse.skalli.services.Services;
import org.eclipse.skalli.services.extension.rest.RestUtils;
import org.eclipse.skalli.view.Consts;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class StaticContentServlet extends HttpServlet {
private static final long serialVersionUID = -1552291510430177634L;
private static final String BASE_DIR = "content";
private static final String TIP_OF_THE_DAY = "/tipoftheday";
private static final String TIP_PREFIX = "tip_";
private static final String TIP_SUFFIX = ".html";
public StaticContentServlet() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String requestURI = request.getRequestURI();
if (requestURI.startsWith("/content/")) {
String path = request.getPathInfo();
if (!path.startsWith("/")) {
path = "/" + path;
}
InputStream in = getContent(path);
if (in != null) {
response.setContentType(getContentType(path));
try {
OutputStream out = response.getOutputStream();
IOUtils.copy(in, out);
} finally {
IOUtils.closeQuietly(in);
}
return;
}
} else if (requestURI.startsWith("/schemas/")) {
// otherwise check the bundles for a matching schema resource
URL schema = RestUtils.findSchemaResource(requestURI);
if (schema != null) {
response.setContentType(getContentType(requestURI));
InputStream in = schema.openStream();
OutputStream out = response.getOutputStream();
try {
resolveIncludes(in, out);
} catch (Exception ex) {
throw new IOException("Failed to resolve schema resource", ex);
} finally {
IOUtils.closeQuietly(in);
}
return;
}
} else if (requestURI.equals(Consts.URL_SEARCH_PLUGIN)) {
URL searchPluginXML = getSearchPluginXML();
if (searchPluginXML != null) {
response.setContentType("application/opensearchdescription+xml"); //$NON-NLS-1$
Properties properties = new Properties();
properties.put(Consts.ATTRIBUTE_SEARCH_PLUGIN_TITLE,
request.getAttribute(Consts.ATTRIBUTE_SEARCH_PLUGIN_TITLE));
properties.put(Consts.ATTRIBUTE_SEARCH_PLUGIN_DESCRIPTION,
request.getAttribute(Consts.ATTRIBUTE_SEARCH_PLUGIN_DESCRIPTION));
properties.put(Consts.ATTRIBUTE_WEBLOCATOR, StringUtils.removeEnd(request.getRequestURL().toString(), requestURI));
InputStream in = searchPluginXML.openStream();
try {
String content = IOUtils.toString(in, "UTF-8"); //$NON-NLS-1$
StrSubstitutor subst = new StrSubstitutor(properties);
content = subst.replace(content);
OutputStream out = response.getOutputStream();
IOUtils.write(content, out, "UTF-8"); //$NON-NLS-1$
} finally {
IOUtils.closeQuietly(in);
}
return;
}
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
private URL getSearchPluginXML() {
List<URL> urls = Services.findResources("/search", "search-plugin.xml", false, FilterMode.FIRST_MATCHING);
return urls.size() == 1 ? urls.get(0) : null;
}
/**
* Resolves all <include> in a given schema and writes the
* result to the given output stream. Includes that can't be resolved
* are removed from the schema.
* @param in the input stream providing the schema to resolve.
* @param out the output stream to write the result to.
* @throws IOException if an i/o error occured.
* @throws SAXException if parsing of the schema failed.
* @throws ParserConfigurationException indicates a serious parser configuration error.
* @throws TransformerException if transforming the schema DOM to a character stream failed.
* @throws TransformerConfigurationException indicates a serious transformer configuration error.
*/
private void resolveIncludes(InputStream in, OutputStream out)
throws IOException, SAXException, ParserConfigurationException,
TransformerConfigurationException, TransformerException
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document schemaDOM = dbf.newDocumentBuilder().parse(new InputSource(in));
Element schemaRoot = schemaDOM.getDocumentElement();
// iterate all <include> tags and resolve them if possible
NodeList includes = schemaDOM.getElementsByTagName("xsd:include");
while (includes.getLength() > 0) {
for (int i = 0; i < includes.getLength(); ++i) {
Node includeNode = includes.item(i);
Node includeParent = includeNode.getParentNode();
Node schemaLocation = includeNode.getAttributes().getNamedItem("schemaLocation");
if (schemaLocation != null)
{
// extract the pure file name from the schemaLocation and
// try to find an XSD resource in all model extensions matching
// the given schemaLocation attribute -> if found, replace the include tag
// with the DOM of the include (without the root tag, of course!)
URL includeFile = RestUtils.findSchemaResource(schemaLocation.getTextContent());
if (includeFile != null) {
Document includeDOM = dbf.newDocumentBuilder().parse(new InputSource(includeFile.openStream()));
NodeList includeNodes = includeDOM.getDocumentElement().getChildNodes();
for (int j = 0; j < includeNodes.getLength(); ++j)
{
// import and insert the tag before <include>
schemaRoot.insertBefore(schemaDOM.importNode(includeNodes.item(j), true), includeNode);
}
}
// in any case: remove the <include> tag
includeParent.removeChild(includeNode);
}
}
// resolve includes of includes (if any)
includes = schemaDOM.getElementsByTagName("xsd:include");
}
// serialize the schema DOM to the given output stream
Transformer xform = TransformerFactory.newInstance().newTransformer();
xform.setOutputProperty(OutputKeys.INDENT, "yes");
xform.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
xform.transform(new DOMSource(schemaDOM), new StreamResult(out));
}
private InputStream getContent(String path) throws IOException {
if (TIP_OF_THE_DAY.equalsIgnoreCase(path)) {
return getRandomFile(BASE_DIR + path, TIP_PREFIX, TIP_SUFFIX);
}
File f = new File(BASE_DIR + path);
return f.exists() && f.isFile() ? new FileInputStream(f) : null;
}
private InputStream getRandomFile(String path, final String prefix, final String suffix) throws IOException {
File dir = new File(path);
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(prefix) && name.endsWith(suffix);
}
});
if (files == null || files.length == 0) {
return null;
}
int next = RandomUtils.nextInt(files.length);
return new FileInputStream(files[next]);
}
private String getContentType(String path) {
if (TIP_OF_THE_DAY.equalsIgnoreCase(path)) {
return "text/html";
} else if (path.endsWith(".png")) {
return "image/png";
} else if (path.endsWith(".gif")) {
return "image/gif";
} else if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
return "image/jpeg";
} else if (path.endsWith(".html") || path.endsWith(".html") || path.endsWith(".htmls")) {
return "text/html";
} else if (path.endsWith(".css")) {
return "text/css";
} else if (path.endsWith(".js")) {
return "text/javascript";
} else if (path.endsWith(".xml") || path.endsWith(".xsd")) {
return "text/xml";
}
return "application/octet-stream";
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
}
}