/*
* Copyright Robert Newson
*
* Licensed 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 com.github.rnewson.couchdb.lucene;
import com.github.rnewson.couchdb.lucene.couchdb.CouchDocument;
import com.github.rnewson.couchdb.lucene.couchdb.Database;
import com.github.rnewson.couchdb.lucene.couchdb.View;
import com.github.rnewson.couchdb.lucene.couchdb.ViewSettings;
import com.github.rnewson.couchdb.lucene.rhino.JSLog;
import com.github.rnewson.couchdb.lucene.rhino.RhinoDocument;
import org.apache.log4j.Logger;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryparser.classic.ParseException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
public final class DocumentConverter {
private static final Collection<Document> NO_DOCUMENTS = Collections.emptyList();
private static final Logger LOG = Logger.getLogger(DocumentConverter.class);
private final Context context;
private final Function viewFun;
private final ScriptableObject scope;
public DocumentConverter(final Context context, final View view) throws IOException, JSONException {
this.context = context;
scope = context.initStandardObjects();
context.setLanguageVersion(Context.VERSION_1_8);
// Allow custom document helper class.
try {
ScriptableObject.defineClass(scope, RhinoDocument.class);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
// Add a log object
ScriptableObject.putProperty(scope, "log", new JSLog());
// Compile user-specified function
try {
viewFun = view.compileFunction(context, scope);
} catch (final RhinoException e) {
LOG.error("View code for " + view + " does not compile.");
throw e;
}
}
public Collection<Document> convert(
final CouchDocument doc,
final ViewSettings defaults,
final Database database) throws IOException, ParseException, JSONException {
final Object result;
final Scriptable scriptable = convertObject(doc.asJson());
try {
result = viewFun.call(context, scope, null, new Object[]{scriptable});
} catch (final JavaScriptException e) {
LOG.warn(doc + " caused exception during conversion.", e);
return NO_DOCUMENTS;
}
if (result == null || result instanceof Undefined) {
return NO_DOCUMENTS;
}
if (result instanceof RhinoDocument) {
final RhinoDocument rhinoDocument = (RhinoDocument) result;
final Document document = rhinoDocument.toDocument(doc.getId(), defaults, database);
return Collections.singleton(document);
}
if (result instanceof NativeArray) {
final NativeArray nativeArray = (NativeArray) result;
final Collection<Document> arrayResult = new ArrayList<>((int) nativeArray.getLength());
for (int i = 0; i < (int) nativeArray.getLength(); i++) {
if (nativeArray.get(i, null) instanceof RhinoDocument) {
final RhinoDocument rhinoDocument = (RhinoDocument) nativeArray.get(i, null);
final Document document = rhinoDocument.toDocument(
doc.getId(),
defaults,
database);
arrayResult.add(document);
}
}
return arrayResult;
}
return null;
}
private Object convert(final Object obj) throws JSONException {
if (obj instanceof JSONArray) {
return convertArray((JSONArray) obj);
} else if (obj == JSONObject.NULL) {
return null;
} else if (obj instanceof JSONObject) {
return convertObject((JSONObject) obj);
} else {
return obj;
}
}
private Scriptable convertArray(final JSONArray array) throws JSONException {
final Scriptable result = context.newArray(scope, array.length());
for (int i = 0, max = array.length(); i < max; i++) {
ScriptableObject.putProperty(result, i, convert(array.get(i)));
}
return result;
}
private Scriptable convertObject(final JSONObject obj) throws JSONException {
if (obj == JSONObject.NULL) {
return null;
}
final Scriptable result = context.newObject(scope);
final Iterator<?> it = obj.keys();
while (it.hasNext()) {
final String key = (String) it.next();
final Object value = obj.get(key);
ScriptableObject.putProperty(result, key, convert(value));
}
return result;
}
}