blob: 873deecad7d3dabdf0f5cac1abba3e41dec00722 [file] [log] [blame]
/***********************************************************************************************************************
* Copyright (c) 2009 empolis GmbH and brox IT Solutions GmbH. 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: Juergen Schumacher (empolis GmbH) - initial API and implementation
**********************************************************************************************************************/
package org.eclipse.smila.search.servlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
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.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.smila.search.api.SearchService;
import org.eclipse.smila.search.api.helper.QueryBuilder;
import org.eclipse.smila.search.api.internal.ResultDocumentBuilder;
import org.eclipse.smila.search.servlet.activator.Activator;
import org.eclipse.smila.search.servlet.solr.IndexNamesHelper;
import org.eclipse.smila.utils.config.ConfigUtils;
import org.eclipse.smila.utils.xml.XMLUtils;
import org.eclipse.smila.utils.xml.XMLUtilsException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* @author jschumacher
*
*/
public class SMILASearchServlet extends HttpServlet {
/**
* default scriptAndFunction name.
*/
public static final String DEFAULT_SCRIPTANDFUNCTION = "search.process";
/**
* default pipeline name.
*/
public static final String DEFAULT_PIPELINE = "SearchPipeline";
/**
* name of parameter specifying the pipeline name.
*/
public static final String PARAM_PIPELINE = "pipeline";
/**
* name of parameter specifying the workflow name.
*/
public static final String PARAM_SCRIPTANDFUNCTION = "script";
/**
* default stylesheet name.
*/
public static final String DEFAULT_STYLESHEET = "SMILASearchDefault";
/**
* Element name for the index names list.
*/
public static final String ELEMENT_INDEX_NAMES = "IndexNames";
/**
* Element name for a index names list entry.
*/
public static final String ELEMENT_INDEX_NAME = "IndexName";
/**
* because it's serializable.
*/
private static final long serialVersionUID = 1L;
/**
* Logging.
*/
private final Log _log = LogFactory.getLog(getClass());
/**
* helper for parsing multipart requests, used to include binary attachments.
*/
private ServletFileUpload _fileUpload;
/**
* helper for converting errors to XML.
*/
private ResultDocumentBuilder _errorBuilder;
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* {@inheritDoc}
*
* @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPut(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
private String getDefaultValue(final String value, final String defaultValue) {
String result = defaultValue;
if ((value != null) && (value.length() > 0)) {
result = value;
}
return result;
}
/**
* Arguments "pipeline" and "script". if both are there, "script" wins. This one is for non-multipart requests
*/
private boolean isScriptingRequestSingle(final HttpServletRequest request, StringBuilder pipelineOrScriptName) {
boolean result = true;
pipelineOrScriptName.append(DEFAULT_SCRIPTANDFUNCTION);
final String pipeVal = request.getParameter(PARAM_PIPELINE);
if (pipeVal != null) {
pipelineOrScriptName.setLength(0);
pipelineOrScriptName.append(getDefaultValue(pipeVal, DEFAULT_PIPELINE));
result = false;
}
final String scriptVal = request.getParameter(PARAM_SCRIPTANDFUNCTION);
if (request.getParameter(PARAM_SCRIPTANDFUNCTION) != null) {
pipelineOrScriptName.setLength(0);
pipelineOrScriptName.append(getDefaultValue(scriptVal, DEFAULT_SCRIPTANDFUNCTION));
result = true;
}
return result;
}
/**
* Arguments "pipeline" and "script". if both are there, "script" wins. This one is for multipart requests
*/
private boolean isScriptingRequestMultiPart(final MultiPartRequestParser parser,
StringBuilder pipelineOrScriptName) {
boolean result = true;
pipelineOrScriptName.append(DEFAULT_SCRIPTANDFUNCTION);
final String pipeVal = parser.getSingleParameterValue(PARAM_PIPELINE);
if (pipeVal != null) {
pipelineOrScriptName.setLength(0);
pipelineOrScriptName.append(getDefaultValue(pipeVal, DEFAULT_PIPELINE));
result = false;
}
final String scriptVal = parser.getSingleParameterValue(PARAM_SCRIPTANDFUNCTION);
if (scriptVal != null) {
pipelineOrScriptName.setLength(0);
pipelineOrScriptName.append(getDefaultValue(scriptVal, DEFAULT_SCRIPTANDFUNCTION));
result = true;
}
return result;
}
private void postProcessResult(final HttpServletResponse response, String stylesheet, final String showXml,
final Document resultDoc) throws ServletException, IOException {
if (StringUtils.isEmpty(stylesheet)) {
// TODO: get default stylesheet name from configuration
stylesheet = DEFAULT_STYLESHEET;
}
try {
byte[] result = null;
if ((showXml != null) && Boolean.valueOf(showXml)) {
response.setContentType("text/xml");
result = XMLUtils.stream(resultDoc.getDocumentElement(), false);
} else {
response.setContentType("text/html;charset=UTF-8");
result = transform(resultDoc, stylesheet);
}
response.getOutputStream().write(result);
response.getOutputStream().flush();
} catch (final XMLUtilsException e) {
if (_log.isErrorEnabled()) {
_log.error("", e);
}
}
}
/**
* extract query parameters from request, create SMILA Query record and send it to a SearchService, transform the DOM.
* Result to HTML using an XSLT stylesheet.
*
* @param request
* HTTP request
* @param response
* HTTP response
* @throws ServletException
* error during processing
* @throws IOException
* error writing result to response stream
*/
protected void processRequest(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
try {
request.setCharacterEncoding("UTF-8");
} catch (final UnsupportedEncodingException e) {
throw new ServletException("unable to set request encoding to UTF-8");
}
final boolean isMultipart = ServletFileUpload.isMultipartContent(request);
boolean scriptingCall = true;
String pipelineOrScriptName = "";
QueryBuilder query = null;
if (isMultipart) {
try {
final List<FileItem> items = getFileUploadParser().parseRequest(request);
final MultiPartRequestParser parser = new MultiPartRequestParser();
parser.initialParse(items);
final StringBuilder sb = new StringBuilder();
scriptingCall = isScriptingRequestMultiPart(parser, sb);
pipelineOrScriptName = sb.toString();
query = parser.parse();
} catch (final FileUploadException ex) {
throw new ServletException("error parsing multipart request", ex);
}
} else {
final HttpRequestParser parser = new HttpRequestParser();
final StringBuilder sb = new StringBuilder();
scriptingCall = isScriptingRequestSingle(request, sb);
pipelineOrScriptName = sb.toString();
query = parser.parse(request);
}
final SearchService searchService = Activator.getInstance().getSearchService();
Document resultDoc = null;
try {
if (searchService == null) {
resultDoc =
getErrorBuilder().buildError(
new ServletException("The SearchService is not available. Please wait a moment and try again."));
} else {
if (scriptingCall) {
resultDoc = searchService.searchAsXmlWithScript(pipelineOrScriptName, query.getQuery());
} else {
resultDoc = searchService.searchAsXml(pipelineOrScriptName, query.getQuery());
}
appendIndexList(resultDoc);
}
} catch (final ParserConfigurationException ex) {
throw new ServletException(
"Error creating an XML result to display. Something is completely wrong in the SMILA setup.", ex);
}
postProcessResult(response, query.getMetadata().getStringValue("style"),
query.getMetadata().getStringValue("showXml"), resultDoc);
}
/**
* transform DOM documment using stylesheet.
*
* @param doc
* DOM search result document
* @param stylesheet
* stylesheet name (without .xsl suffix)
* @return HTML result
* @throws ServletException
* error during transformation
*/
protected byte[] transform(final Document doc, final String stylesheet) throws ServletException {
final DOMSource xmlDomSource = new DOMSource(doc);
// sw recieves the transformation's result.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final StreamResult stream = new StreamResult(baos);
// transform via StreamResult into the StringWriter
InputStream is = null;
try {
is = ConfigUtils.getConfigStream(Activator.BUNDLE_NAME, stylesheet + ".xsl");
final Document xslDoc = XMLUtils.parse(is, false);
final Transformer transformer = getXSLTransformer(xslDoc);
transformer.setParameter("stylesheet", stylesheet);
transformer.transform(xmlDomSource, stream);
} catch (final XMLUtilsException xue) {
throw new ServletException("the stylesheet is not valid [" + stylesheet + ".xsl]", xue);
} catch (final TransformerException te) {
throw new ServletException("Error ocured while transforming!", te);
} finally {
IOUtils.closeQuietly(is);
}
return baos.toByteArray();
}
/**
* create XSL transformer for stylesheet.
*
* @param xslDoc
* XSL DOM document
* @return XSL transformer
* @throws ServletException
* error.
*/
protected Transformer getXSLTransformer(final Document xslDoc) throws ServletException {
final TransformerFactory tFactory = TransformerFactory.newInstance();
if (tFactory.getFeature(DOMSource.FEATURE) && tFactory.getFeature(StreamResult.FEATURE)) {
final DOMSource xslDomSource = new DOMSource(xslDoc);
try {
return tFactory.newTransformer(xslDomSource);
} catch (final TransformerConfigurationException e) {
throw new ServletException("error while creating the transformer", e);
}
} else {
throw new ServletException("the transformer [" + tFactory.getClass().getName()
+ "] doesn't support the used DOMSource or StreamResult");
}
}
/**
* Appends a list of index names to the document.
*
* @param doc
* the document to append the list to
*/
protected void appendIndexList(final Document doc) {
Collection<String> indexNames = null;
try {
indexNames = IndexNamesHelper.getIndexNames();
} catch (final InterruptedException exception) {
if (_log.isErrorEnabled()) {
_log.error("unable to append index list", exception);
}
}
if (indexNames != null) {
final Element indexNamesElement = doc.createElementNS(SearchService.NAMESPACE_SEARCH, ELEMENT_INDEX_NAMES);
for (final String coreName : indexNames) {
if (coreName != null) {
final Element nameElement = doc.createElement(ELEMENT_INDEX_NAME);
nameElement.setTextContent(coreName);
indexNamesElement.appendChild(nameElement);
}
}
doc.getDocumentElement().appendChild(indexNamesElement);
}
}
/**
* ensure and return a file upload parser.
*
* @return FileUpload helper.
*/
protected synchronized ServletFileUpload getFileUploadParser() {
if (_fileUpload == null) {
// Create a factory for disk-based file items
final FileItemFactory factory = new DiskFileItemFactory(Integer.MAX_VALUE, null);
// Create a new file upload handler
_fileUpload = new ServletFileUpload(factory);
// Parse the request. List of FileItem
}
return _fileUpload;
}
/**
* ensure and return a ResultDocumentBuilder.
*
* @return ResultDocumentBuilder helper.
*/
protected synchronized ResultDocumentBuilder getErrorBuilder() {
if (_errorBuilder == null) {
_errorBuilder = new ResultDocumentBuilder();
}
return _errorBuilder;
}
}