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 ); } }