content: package org.apache.solr.handler;
  /**
   * 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.
   */
  
  import java.io.IOException;
  import java.io.Reader;
  import java.io.StringReader;
  import java.util.Stack;
  
  import org.apache.commons.io.IOUtils;
  import org.apache.noggit.JSONParser;
  import org.apache.solr.common.SolrException;
  import org.apache.solr.common.SolrInputDocument;
  import org.apache.solr.common.SolrInputField;
  import org.apache.solr.common.params.UpdateParams;
  import org.apache.solr.common.util.ContentStream;
  import org.apache.solr.request.SolrQueryRequest;
  import org.apache.solr.response.SolrQueryResponse;
  import org.apache.solr.update.AddUpdateCommand;
  import org.apache.solr.update.CommitUpdateCommand;
  import org.apache.solr.update.DeleteUpdateCommand;
  import org.apache.solr.update.RollbackUpdateCommand;
  import org.apache.solr.update.processor.UpdateRequestProcessor;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  
  /**
   * @since solr 3.1
   */
  class JsonLoader extends ContentStreamLoader {
    final static Logger log = LoggerFactory.getLogger( JsonLoader.class );
    
    protected final UpdateRequestProcessor processor;
    protected final SolrQueryRequest req;
    protected JSONParser parser;
    protected final int commitWithin;
    protected final boolean overwrite;
  
    public JsonLoader(SolrQueryRequest req, UpdateRequestProcessor processor) {
      this.processor = processor;
      this.req = req;
  
      commitWithin = req.getParams().getInt(UpdateParams.COMMIT_WITHIN, -1);
      overwrite = req.getParams().getBool(XmlUpdateRequestHandler.OVERWRITE, true);
    }
  
    @Override
    public void load(SolrQueryRequest req, SolrQueryResponse rsp, ContentStream stream) throws Exception {
      errHeader = "JSONLoader: " + stream.getSourceInfo();
      Reader reader = null;
      try {
        reader = stream.getReader();
        if (log.isTraceEnabled()) {
          String body = IOUtils.toString(reader);
          log.trace("body", body);
          reader = new StringReader(body);
        }
  
        parser = new JSONParser(reader);
        this.processUpdate();
      }
      finally {
        IOUtils.closeQuietly(reader);
      }
    }
  
    @SuppressWarnings("fallthrough")
    void processUpdate() throws IOException
    {
      int ev = parser.nextEvent();
      while( ev != JSONParser.EOF ) {
        
        switch( ev )
        {
          case JSONParser.ARRAY_START:
            handleAdds();
            break;
  
        case JSONParser.STRING:
          if( parser.wasKey() ) {
            String v = parser.getString();
            if( v.equals( XmlUpdateRequestHandler.ADD ) ) {
              int ev2 = parser.nextEvent();
              if (ev2 == JSONParser.OBJECT_START) {
                processor.processAdd( parseAdd() );
              } else if (ev2 == JSONParser.ARRAY_START) {
                handleAdds();
              } else {
                assertEvent(ev2, JSONParser.OBJECT_START);
              }
            }
            else if( v.equals( XmlUpdateRequestHandler.COMMIT ) ) {
              CommitUpdateCommand cmd = new CommitUpdateCommand(false);
              cmd.waitFlush = cmd.waitSearcher = true;
              parseCommitOptions( cmd );
              processor.processCommit( cmd );
            }
            else if( v.equals( XmlUpdateRequestHandler.OPTIMIZE ) ) {
              CommitUpdateCommand cmd = new CommitUpdateCommand(true);
              cmd.waitFlush = cmd.waitSearcher = true;
              parseCommitOptions( cmd );
              processor.processCommit( cmd );
            }
            else if( v.equals( XmlUpdateRequestHandler.DELETE ) ) {
              processor.processDelete( parseDelete() );
            }
            else if( v.equals( XmlUpdateRequestHandler.ROLLBACK ) ) {
              processor.processRollback( parseRollback() );
            }
            else {
              throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown command: "+v+" ["+parser.getPosition()+"]" );
            }
            break;
          }
          // fall through
  
        case JSONParser.LONG:
        case JSONParser.NUMBER:
        case JSONParser.BIGNUMBER:
        case JSONParser.BOOLEAN:
        case JSONParser.NULL:
          log.info( "can't have a value here! "
              +JSONParser.getEventString(ev)+" "+parser.getPosition() );
          
        case JSONParser.OBJECT_START:
        case JSONParser.OBJECT_END:
        case JSONParser.ARRAY_END:
          break;
          
        default:
          log.info("Noggit UNKNOWN_EVENT_ID:"+ev);
          break;
        }
        // read the next event
        ev = parser.nextEvent();
      }
    }
  
    DeleteUpdateCommand parseDelete() throws IOException {
      assertNextEvent( JSONParser.OBJECT_START );
  
      DeleteUpdateCommand cmd = new DeleteUpdateCommand();
      cmd.fromCommitted = cmd.fromPending = true;
  
      while( true ) {
        int ev = parser.nextEvent();
        if( ev == JSONParser.STRING ) {
          String key = parser.getString();
          if( parser.wasKey() ) {
            if( "id".equals( key ) ) {
              cmd.id = parser.getString();
            }
            else if( "query".equals(key) ) {
              cmd.query = parser.getString();
            }
            else {
              throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown key: "+key+" ["+parser.getPosition()+"]" );
            }
          }
          else {
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                "invalid string: " + key 
                +" at ["+parser.getPosition()+"]" );
          }
        }
        else if( ev == JSONParser.OBJECT_END ) {
          if( cmd.id == null && cmd.query == null ) {
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing id or query for delete ["+parser.getPosition()+"]" );
          }
          return cmd;
        }
        else {
          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
              "Got: "+JSONParser.getEventString( ev  )
              +" at ["+parser.getPosition()+"]" );
        }
      }
    }
    
    RollbackUpdateCommand parseRollback() throws IOException {
      assertNextEvent( JSONParser.OBJECT_START );
      assertNextEvent( JSONParser.OBJECT_END );
      return new RollbackUpdateCommand();
    }
  
    void parseCommitOptions(CommitUpdateCommand cmd ) throws IOException
    {
      assertNextEvent( JSONParser.OBJECT_START );
  
      while( true ) {
        int ev = parser.nextEvent();
        if( ev == JSONParser.STRING ) {
          String key = parser.getString();
          if( parser.wasKey() ) {
            if( XmlUpdateRequestHandler.WAIT_SEARCHER.equals( key ) ) {
              cmd.waitSearcher = parser.getBoolean();
            }
            else if( XmlUpdateRequestHandler.WAIT_FLUSH.equals( key ) ) {
              cmd.waitFlush = parser.getBoolean();
            }
            else {
              throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown key: "+key+" ["+parser.getPosition()+"]" );
            }
          }
          else {
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                "invalid string: " + key 
                +" at ["+parser.getPosition()+"]" );
          }
        }
        else if( ev == JSONParser.OBJECT_END ) {
          return;
        }
        else {
          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
              "Got: "+JSONParser.getEventString( ev  )
              +" at ["+parser.getPosition()+"]" );
        }
      }
    }
    
    AddUpdateCommand parseAdd() throws IOException
    {
      AddUpdateCommand cmd = new AddUpdateCommand();
      cmd.commitWithin = commitWithin;
      cmd.overwriteCommitted = cmd.overwritePending = overwrite;
      cmd.allowDups = !overwrite;
  
      float boost = 1.0f;
      
      while( true ) {
        int ev = parser.nextEvent();
        if( ev == JSONParser.STRING ) {
          if( parser.wasKey() ) {
            String key = parser.getString();
            if( "doc".equals( key ) ) {
              if( cmd.solrDoc != null ) {
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "multiple docs in same add command" );
              }
              ev = assertNextEvent( JSONParser.OBJECT_START );
              cmd.solrDoc = parseDoc( ev );
            }
            else if( XmlUpdateRequestHandler.OVERWRITE.equals( key ) ) {
              cmd.allowDups = !parser.getBoolean(); // reads next boolean
              cmd.overwriteCommitted = cmd.overwritePending = !cmd.allowDups;
            }
            else if( XmlUpdateRequestHandler.COMMIT_WITHIN.equals( key ) ) {
              cmd.commitWithin = (int)parser.getLong();
            }
            else if( "boost".equals( key ) ) {
              boost = Float.parseFloat( parser.getNumberChars().toString() );
            }
            else {
              throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown key: "+key+" ["+parser.getPosition()+"]" );
            }
          }
          else {
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                "Should be a key "
                +" at ["+parser.getPosition()+"]" );
          }
        }
        else if( ev == JSONParser.OBJECT_END ) {
          if( cmd.solrDoc == null ) {
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,"missing solr document. "+parser.getPosition() );
          }
          cmd.solrDoc.setDocumentBoost( boost ); 
          return cmd;
        }
        else {
          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
              "Got: "+JSONParser.getEventString( ev  )
              +" at ["+parser.getPosition()+"]" );
        }
      }
    }
  
  
    void handleAdds() throws IOException
    {
      while( true ) {
        AddUpdateCommand cmd = new AddUpdateCommand();
        cmd.commitWithin = commitWithin;
        cmd.overwriteCommitted = cmd.overwritePending = overwrite;
        cmd.allowDups = !overwrite;
  
        int ev = parser.nextEvent();
        if (ev == JSONParser.ARRAY_END) break;
  
        assertEvent(ev, JSONParser.OBJECT_START);
        cmd.solrDoc = parseDoc(ev);
        processor.processAdd(cmd);
      }
    }
  
  
    int assertNextEvent(int expected ) throws IOException
    {
      int got = parser.nextEvent();
      assertEvent(got, expected);
      return got;
    }
  
    void assertEvent(int ev, int expected) {
      if( ev != expected ) {
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
            "Expected: "+JSONParser.getEventString( expected  )
            +" but got "+JSONParser.getEventString( ev )
            +" at ["+parser.getPosition()+"]" );
      }
    }
    
    SolrInputDocument parseDoc(int ev) throws IOException
    {
      Stack<Object> stack = new Stack<Object>();
      Object obj = null;
      boolean inArray = false;
  
      if( ev != JSONParser.OBJECT_START ) {
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "object should already be started" );
      }
      
      while( true ) {
        //System.out.println( ev + "["+JSONParser.getEventString(ev)+"] "+parser.wasKey() ); //+ parser.getString() );
  
        switch (ev) {
          case JSONParser.STRING:
            if( parser.wasKey() ) {
              obj = stack.peek();
              String v = parser.getString();
              if( obj instanceof SolrInputField ) {
                SolrInputField field = (SolrInputField)obj;
                if( "boost".equals( v ) ) {
                  ev = parser.nextEvent();
                  if( ev != JSONParser.NUMBER &&
                      ev != JSONParser.LONG &&  
                      ev != JSONParser.BIGNUMBER ) {
                    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "boost should have number! "+JSONParser.getEventString(ev) );
                  }
                  field.setBoost((float)parser.getDouble());
                }
                else if( "value".equals( v  ) ) {
                  // nothing special...
                  stack.push( field ); // so it can be popped
                }
                else {
                  throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "invalid key: "+v + " ["+ parser.getPosition()+"]" );
                }
              }
              else if( obj instanceof SolrInputDocument ) {
                SolrInputDocument doc = (SolrInputDocument)obj;
                SolrInputField f = doc.get( v );
                if( f == null ) {
                  f = new SolrInputField( v );
                  doc.put( f.getName(), f );
                }
                stack.push( f );
              }
              else {
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "hymmm ["+ parser.getPosition()+"]" );
              }
            }
            else {
              addValToField(stack, parser.getString(), inArray, parser);
            }
            break;
  
          case JSONParser.LONG:
          case JSONParser.NUMBER:
          case JSONParser.BIGNUMBER:
            addValToField(stack, parser.getNumberChars().toString(), inArray, parser);
            break;
            
          case JSONParser.BOOLEAN:
            addValToField(stack, parser.getBoolean(),inArray, parser);
            break;
  
          case JSONParser.NULL:
            parser.getNull();
            /*** if we wanted to remove the field from the document now...
            if (!inArray) {
              Object o = stack.peek();
              // if null was only value in the field, then remove the field
              if (o instanceof SolrInputField) {
                SolrInputField sif = (SolrInputField)o;
                if (sif.getValueCount() == 0) {
                  sdoc.remove(sif.getName());
                }
              }
            }
            ***/
  
            addValToField(stack, null, inArray, parser);
            break;
  
          case JSONParser.OBJECT_START:
            if( stack.isEmpty() ) {
              stack.push( new SolrInputDocument() );
            }
            else {
              obj = stack.peek();
              if( obj instanceof SolrInputField ) {
                // should alreay be pushed...
              }
              else {
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "should not start new object with: "+obj + " ["+ parser.getPosition()+"]" );
              }
            }
            break;
          case JSONParser.OBJECT_END:
            obj = stack.pop();
            if( obj instanceof SolrInputDocument ) {
              return (SolrInputDocument)obj;
            }
            else if( obj instanceof SolrInputField ) {
              // should already be pushed...
            }
            else {
              throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "should not start new object with: "+obj + " ["+ parser.getPosition()+"]" );
            }
            break;
  
          case JSONParser.ARRAY_START:
            inArray = true;
            break;
            
          case JSONParser.ARRAY_END:
            inArray = false;
            stack.pop(); // the val should have done it...
            break;
            
          default:
            System.out.println("UNKNOWN_EVENT_ID:"+ev);
            break;
        }
  
        ev = parser.nextEvent();
        if( ev == JSONParser.EOF ) {
          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "should finish doc first!" );
        }
      }
    }
    
    static void addValToField( Stack stack, Object val, boolean inArray, JSONParser parser ) throws IOException
    {
      Object obj = stack.peek();
      if( !(obj instanceof SolrInputField) ) {
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "hymmm ["+parser.getPosition()+"]" );
      }
  
      SolrInputField f = inArray
        ? (SolrInputField)obj
        : (SolrInputField)stack.pop();
  
      if (val == null) return;
  
      float boost = (f.getValue()==null)?f.getBoost():1.0f;
      f.addValue( val,boost );
    }
  
  
  }
  