/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr.servlet; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.MultiMapSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.core.Config; import org.apache.solr.core.SolrCore; import org.apache.solr.request.ServletSolrParams; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrQueryRequestBase; public class SolrRequestParsers { final Logger log = LoggerFactory.getLogger(SolrRequestParsers.class); // Should these constants be in a more public place? public static final String MULTIPART = "multipart"; public static final String RAW = "raw"; public static final String SIMPLE = "simple"; public static final String STANDARD = "standard"; private HashMap<String, SolrRequestParser> parsers; private boolean enableRemoteStreams = false; private boolean handleSelect = true; private StandardRequestParser standard; /** * Pass in an xml configuration. A null configuration will enable * everythign with maximum values. */ public SolrRequestParsers( Config globalConfig ) { long uploadLimitKB = 1048; // 2MB default if( globalConfig == null ) { uploadLimitKB = Long.MAX_VALUE; enableRemoteStreams = true; handleSelect = true; } else { uploadLimitKB = globalConfig.getInt( "requestDispatcher/requestParsers/@multipartUploadLimitInKB", (int)uploadLimitKB ); enableRemoteStreams = globalConfig.getBool( "requestDispatcher/requestParsers/@enableRemoteStreaming", false ); // Let this filter take care of /select?xxx format handleSelect = globalConfig.getBool( "requestDispatcher/@handleSelect", handleSelect ); } MultipartRequestParser multi = new MultipartRequestParser( uploadLimitKB ); RawRequestParser raw = new RawRequestParser(); standard = new StandardRequestParser( multi, raw ); // I don't see a need to have this publicly configured just yet // adding it is trivial parsers = new HashMap<String, SolrRequestParser>(); parsers.put( MULTIPART, multi ); parsers.put( RAW, raw ); parsers.put( SIMPLE, new SimpleRequestParser() ); parsers.put( STANDARD, standard ); parsers.put( "", standard ); } public SolrQueryRequest parse( SolrCore core, String path, HttpServletRequest req ) throws Exception { SolrRequestParser parser = standard; // TODO -- in the future, we could pick a different parser based on the request // Pick the parser from the request... ArrayList<ContentStream> streams = new ArrayList<ContentStream>(1); SolrParams params = parser.parseParamsAndFillStreams( req, streams ); SolrQueryRequest sreq = buildRequestFrom( core, params, streams ); // Handlers and login will want to know the path. If it contains a ':' // the handler could use it for RESTful URLs sreq.getContext().put( "path", path ); return sreq; } public SolrQueryRequest buildRequestFrom( SolrCore core, SolrParams params, Collection<ContentStream> streams ) throws Exception { // The content type will be applied to all streaming content String contentType = params.get( CommonParams.STREAM_CONTENTTYPE ); // Handle anything with a remoteURL String[] strs = params.getParams( CommonParams.STREAM_URL ); if( strs != null ) { if( !enableRemoteStreams ) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Remote Streaming is disabled." ); } for( final String url : strs ) { ContentStreamBase stream = new ContentStreamBase.URLStream( new URL(url) ); if( contentType != null ) { stream.setContentType( contentType ); } streams.add( stream ); } } // Handle streaming files strs = params.getParams( CommonParams.STREAM_FILE ); if( strs != null ) { if( !enableRemoteStreams ) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Remote Streaming is disabled." ); } for( final String file : strs ) { ContentStreamBase stream = new ContentStreamBase.FileStream( new File(file) ); if( contentType != null ) { stream.setContentType( contentType ); } streams.add( stream ); } } // Check for streams in the request parameters strs = params.getParams( CommonParams.STREAM_BODY ); if( strs != null ) { for( final String body : strs ) { ContentStreamBase stream = new ContentStreamBase.StringStream( body ); if( contentType != null ) { stream.setContentType( contentType ); } streams.add( stream ); } } SolrQueryRequestBase q = new SolrQueryRequestBase( core, params ) { }; if( streams != null && streams.size() > 0 ) { q.setContentStreams( streams ); } return q; } /** * Given a standard query string map it into solr params */ public static MultiMapSolrParams parseQueryString(String queryString) { Map<String,String[]> map = new HashMap<String, String[]>(); if( queryString != null && queryString.length() > 0 ) { try { for( String kv : queryString.split( "&" ) ) { int idx = kv.indexOf( '=' ); if( idx > 0 ) { String name = URLDecoder.decode( kv.substring( 0, idx ), "UTF-8"); String value = URLDecoder.decode( kv.substring( idx+1 ), "UTF-8"); MultiMapSolrParams.addParam( name, value, map ); } else { String name = URLDecoder.decode( kv, "UTF-8" ); MultiMapSolrParams.addParam( name, "", map ); } } } catch( UnsupportedEncodingException uex ) { throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, uex ); } } return new MultiMapSolrParams( map ); } public boolean isHandleSelect() { return handleSelect; } public void setHandleSelect(boolean handleSelect) { this.handleSelect = handleSelect; } } //----------------------------------------------------------------- //----------------------------------------------------------------- // I guess we don't really even need the interface, but i'll keep it here just for kicks interface SolrRequestParser { public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception; } //----------------------------------------------------------------- //----------------------------------------------------------------- /** * The simple parser just uses the params directly */ class SimpleRequestParser implements SolrRequestParser { public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception { return new ServletSolrParams(req); } } /** * Wrap an HttpServletRequest as a ContentStream */ class HttpRequestContentStream extends ContentStreamBase { private final HttpServletRequest req; public HttpRequestContentStream( HttpServletRequest req ) throws IOException { this.req = req; contentType = req.getContentType(); // name = ??? // sourceInfo = ??? String v = req.getHeader( "Content-Length" ); if( v != null ) { size = Long.valueOf( v ); } } public InputStream getStream() throws IOException { return req.getInputStream(); } } /** * Wrap a FileItem as a ContentStream */ class FileItemContentStream extends ContentStreamBase { private final FileItem item; public FileItemContentStream( FileItem f ) { item = f; contentType = item.getContentType(); name = item.getName(); sourceInfo = item.getFieldName(); size = item.getSize(); } public InputStream getStream() throws IOException { return item.getInputStream(); } } /** * The raw parser just uses the params directly */ class RawRequestParser implements SolrRequestParser { public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception { // The javadocs for HttpServletRequest are clear that req.getReader() should take // care of any character encoding issues. BUT, there are problems while running on // some servlet containers: including Tomcat 5 and resin. // // Rather than return req.getReader(), this uses the default ContentStreamBase method // that checks for charset definitions in the ContentType. streams.add( new HttpRequestContentStream( req ) ); return SolrRequestParsers.parseQueryString( req.getQueryString() ); } } /** * Extract Multipart streams */ class MultipartRequestParser implements SolrRequestParser { private long uploadLimitKB; public MultipartRequestParser( long limit ) { uploadLimitKB = limit; } public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception { if( !ServletFileUpload.isMultipartContent(req) ) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Not multipart content! "+req.getContentType() ); } MultiMapSolrParams params = SolrRequestParsers.parseQueryString( req.getQueryString() ); // Create a factory for disk-based file items DiskFileItemFactory factory = new DiskFileItemFactory(); // Set factory constraints // TODO - configure factory.setSizeThreshold(yourMaxMemorySize); // TODO - configure factory.setRepository(yourTempDirectory); // Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(factory); upload.setSizeMax( uploadLimitKB*1024 ); // Parse the request List items = upload.parseRequest(req); Iterator iter = items.iterator(); while (iter.hasNext()) { FileItem item = (FileItem) iter.next(); // If its a form field, put it in our parameter map if (item.isFormField()) { MultiMapSolrParams.addParam( item.getFieldName(), item.getString(), params.getMap() ); } // Only add it if it actually has something... else if( item.getSize() > 0 ) { streams.add( new FileItemContentStream( item ) ); } } return params; } } /** * The default Logic */ class StandardRequestParser implements SolrRequestParser { MultipartRequestParser multipart; RawRequestParser raw; StandardRequestParser( MultipartRequestParser multi, RawRequestParser raw ) { this.multipart = multi; this.raw = raw; } public SolrParams parseParamsAndFillStreams( final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception { String method = req.getMethod().toUpperCase(Locale.ENGLISH); if( "GET".equals( method ) || "HEAD".equals( method )) { return new ServletSolrParams(req); } if( "POST".equals( method ) ) { String contentType = req.getContentType(); if( contentType != null ) { int idx = contentType.indexOf( ';' ); if( idx > 0 ) { // remove the charset definition "; charset=utf-8" contentType = contentType.substring( 0, idx ); } if( "application/x-www-form-urlencoded".equals( contentType.toLowerCase(Locale.ENGLISH) ) ) { return new ServletSolrParams(req); // just get the params from parameterMap } if( ServletFileUpload.isMultipartContent(req) ) { return multipart.parseParamsAndFillStreams(req, streams); } } return raw.parseParamsAndFillStreams(req, streams); } throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Unsupported method: "+method ); } }