content: /**
   * 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() );
          }
          // Add the stream
          else { 
              System.out.println(item.getFieldName()+"==="+item.getSize());
            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 );
    }
  }
  
  
  
  
  
  
  
  
  
  