Java tutorial
/** * Copyright (c) 2013 Nick Lloyd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.nlloyd.hornofmongo.util; import com.github.nlloyd.hornofmongo.MongoRuntime; import com.github.nlloyd.hornofmongo.MongoScope; import com.github.nlloyd.hornofmongo.action.MongoAction; import com.github.nlloyd.hornofmongo.action.NewInstanceAction; import com.github.nlloyd.hornofmongo.adaptor.BinData; import com.github.nlloyd.hornofmongo.adaptor.DBRef; import com.github.nlloyd.hornofmongo.adaptor.MaxKey; import com.github.nlloyd.hornofmongo.adaptor.MinKey; import com.github.nlloyd.hornofmongo.adaptor.NumberInt; import com.github.nlloyd.hornofmongo.adaptor.NumberLong; import com.github.nlloyd.hornofmongo.adaptor.ObjectId; import com.github.nlloyd.hornofmongo.adaptor.ScriptableMongoObject; import com.github.nlloyd.hornofmongo.adaptor.Timestamp; import com.github.nlloyd.hornofmongo.exception.MongoScopeException; import com.mongodb.BasicDBObject; import com.mongodb.Bytes; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; import org.bson.BSON; import org.bson.BSONObject; import org.bson.types.BSONTimestamp; import org.bson.types.Binary; import org.bson.types.Code; import org.bson.types.Symbol; import org.mozilla.javascript.BaseFunction; import org.mozilla.javascript.ConsString; import org.mozilla.javascript.Context; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeObject; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; import org.mozilla.javascript.Wrapper; import org.mozilla.javascript.regexp.NativeRegExp; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.regex.Pattern; //GC: added 17/11/15 /** * @author nlloyd * */ public class BSONizer { public static Object convertJStoBSON(Object jsObject, boolean isJsObj) { return convertJStoBSON(jsObject, isJsObj, null); } public static Object convertJStoBSON(Object jsObject, boolean isJsObj, String dateFormat) { Object bsonObject = null; if (jsObject instanceof NativeArray) { NativeArray jsArray = (NativeArray) jsObject; List<Object> bsonArray = new ArrayList<Object>(Long.valueOf(jsArray.getLength()).intValue()); for (Object jsEntry : jsArray) { bsonArray.add(convertJStoBSON(jsEntry, isJsObj, dateFormat)); } bsonObject = bsonArray; } else if (jsObject instanceof NativeRegExp) { Object source = ScriptableObject.getProperty((Scriptable) jsObject, "source"); String fullRegex = (String) Context.jsToJava(jsObject, String.class); String options = fullRegex.substring(fullRegex.lastIndexOf("/") + 1); bsonObject = Pattern.compile(source.toString(), Bytes.regexFlags(options)); ; } else if (jsObject instanceof NativeObject) { BasicDBObject bson = new BasicDBObject(); bsonObject = bson; NativeObject rawJsObject = (NativeObject) jsObject; for (Object key : rawJsObject.keySet()) { Object value = extractJSProperty(rawJsObject, key); //GC: 17/11/15 allow for UTC $date object if (key.equals("$date")) { try { bsonObject = DateUtils.parseDate(value.toString(), new String[] { "yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" }); } catch (java.text.ParseException e) { bson.put(key.toString(), convertJStoBSON(value, isJsObj, dateFormat)); } } else { bson.put(key.toString(), convertJStoBSON(value, isJsObj, dateFormat)); } } } else if (jsObject instanceof ScriptableMongoObject) { bsonObject = convertScriptableMongoToBSON((ScriptableMongoObject) jsObject, isJsObj, dateFormat); } else if (jsObject instanceof BaseFunction) { BaseFunction funcObject = (BaseFunction) jsObject; Object classPrototype = ScriptableObject.getClassPrototype(funcObject, funcObject.getFunctionName()); if ((classPrototype instanceof MinKey) || (classPrototype instanceof MaxKey)) { // this is a special case handler for instances where MinKey or // MaxKey are provided without explicit constructor calls // index_check3.js does this bsonObject = convertScriptableMongoToBSON((ScriptableMongoObject) classPrototype, isJsObj, dateFormat); } else { // comes from eval calls String decompiledCode = (String) MongoRuntime.call(new JSDecompileAction(funcObject)); bsonObject = new Code(decompiledCode); } } else if (jsObject instanceof ScriptableObject) { // we found a ScriptableObject that isn't any of the concrete // ScriptableObjects above... String jsClassName = ((ScriptableObject) jsObject).getClassName(); if ("Date".equals(jsClassName)) { Date dt = (Date) Context.jsToJava(jsObject, Date.class); bsonObject = dt; //GC: 18/11/15 use dateFormat parameter to format date fields if (dateFormat != null && dateFormat.length() > 0) bsonObject = DateFormatUtils.formatUTC(dt, dateFormat); } else { Context.throwAsScriptRuntimeEx( new MongoScopeException("bsonizer couldnt convert js class: " + jsClassName)); bsonObject = jsObject; } } else if (jsObject instanceof ConsString) { bsonObject = jsObject.toString(); } else if (jsObject instanceof Undefined) { bsonObject = jsObject; } else if (jsObject instanceof Integer) { // this may seem strange, but JavaScript only knows about the number // type // which means in the official client we need to pass a Double // this applies to Long and Integer values bsonObject = Double.valueOf((Integer) jsObject); } else if (jsObject instanceof Long) { bsonObject = Double.valueOf((Long) jsObject); } else { bsonObject = jsObject; } return bsonObject; } @SuppressWarnings("deprecation") public static Object convertBSONtoJS(MongoScope mongoScope, Object bsonObject) { Object jsObject = null; if (bsonObject instanceof List<?>) { List<?> bsonList = (List<?>) bsonObject; Scriptable jsArray = (Scriptable) MongoRuntime.call(new NewInstanceAction(mongoScope, bsonList.size())); int index = 0; for (Object bsonEntry : bsonList) { ScriptableObject.putProperty(jsArray, index, convertBSONtoJS(mongoScope, bsonEntry)); index++; } jsObject = jsArray; } else if (bsonObject instanceof BSONObject) { Scriptable jsObj = (Scriptable) MongoRuntime.call(new NewInstanceAction(mongoScope)); BSONObject bsonObj = (BSONObject) bsonObject; for (String key : bsonObj.keySet()) { Object value = convertBSONtoJS(mongoScope, bsonObj.get(key)); MongoRuntime.call(new JSPopulatePropertyAction(jsObj, key, value)); } jsObject = jsObj; } else if (bsonObject instanceof Symbol) { jsObject = ((Symbol) bsonObject).getSymbol(); } else if (bsonObject instanceof Date) { jsObject = MongoRuntime.call( new NewInstanceAction(mongoScope, "Date", new Object[] { ((Date) bsonObject).getTime() })); } else if (bsonObject instanceof Pattern) { Pattern regex = (Pattern) bsonObject; String source = regex.pattern(); String options = Bytes.regexFlags(regex.flags()); jsObject = MongoRuntime .call(new NewInstanceAction(mongoScope, "RegExp", new Object[] { source, options })); } else if (bsonObject instanceof org.bson.types.ObjectId) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "ObjectId")); ((ObjectId) jsObject).setRealObjectId((org.bson.types.ObjectId) bsonObject); } else if (bsonObject instanceof org.bson.types.MinKey) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "MinKey")); } else if (bsonObject instanceof org.bson.types.MaxKey) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "MaxKey")); } else if (bsonObject instanceof com.mongodb.DBRef) { com.mongodb.DBRef dbRef = (com.mongodb.DBRef) bsonObject; Object id = convertBSONtoJS(mongoScope, dbRef.getId()); jsObject = MongoRuntime.call( new NewInstanceAction(mongoScope, "DBRef", new Object[] { dbRef.getCollectionName(), id })); } else if (bsonObject instanceof BSONTimestamp) { BSONTimestamp bsonTstamp = (BSONTimestamp) bsonObject; jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "Timestamp", new Object[] { bsonTstamp.getTime(), bsonTstamp.getInc() })); } else if (bsonObject instanceof Long) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "NumberLong")); ((NumberLong) jsObject).setRealLong((Long) bsonObject); } else if (bsonObject instanceof Integer) { jsObject = Double.valueOf((Integer) bsonObject); } else if (bsonObject instanceof Code) { jsObject = ((Code) bsonObject).getCode(); } else if (bsonObject instanceof byte[]) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "BinData")); ((BinData) jsObject).setValues(0, (byte[]) bsonObject); } else if (bsonObject instanceof Binary) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "BinData")); ((BinData) jsObject).setValues(((Binary) bsonObject).getType(), ((Binary) bsonObject).getData()); } else if (bsonObject instanceof UUID) { jsObject = MongoRuntime.call(new NewInstanceAction(mongoScope, "BinData")); UUID uuid = (UUID) bsonObject; ByteBuffer dataBuffer = ByteBuffer.allocate(16); // mongodb wire protocol is little endian dataBuffer.order(ByteOrder.LITTLE_ENDIAN); dataBuffer.putLong(uuid.getMostSignificantBits()); dataBuffer.putLong(uuid.getLeastSignificantBits()); ((BinData) jsObject).setValues(BSON.B_UUID, dataBuffer.array()); } else { jsObject = bsonObject; } return jsObject; } /** * Ammended form of the {@link ScriptableObject#get(Object)} method that * will return {@link Undefined} property values instead of null. * * @param jsObject * @param key * @return */ private static Object extractJSProperty(ScriptableObject jsObject, Object key) { Object value = null; if (key instanceof String) { value = jsObject.get((String) key, jsObject); } else if (key instanceof Number) { value = jsObject.get(((Number) key).intValue(), jsObject); } if (value == Scriptable.NOT_FOUND) { return null; } else if (value instanceof Wrapper) { return ((Wrapper) value).unwrap(); } else { return value; } } @SuppressWarnings("deprecation") private static Object convertScriptableMongoToBSON(ScriptableMongoObject jsMongoObj, boolean isJsObj, String dateFormat) { Object bsonObject = null; if (jsMongoObj instanceof ObjectId) { bsonObject = ((ObjectId) jsMongoObj).getRealObjectId(); } else if (jsMongoObj instanceof BinData) { BinData binData = (BinData) jsMongoObj; byte type = new Integer(binData.getType()).byteValue(); byte[] data = binData.getDataBytes(); if (type == BSON.B_UUID) { ByteBuffer dataBuffer = ByteBuffer.wrap(data); // mongodb wire protocol is little endian dataBuffer.order(ByteOrder.LITTLE_ENDIAN); long mostSigBits = dataBuffer.getLong(); long leastSigBits = dataBuffer.getLong(); bsonObject = new UUID(mostSigBits, leastSigBits); } else bsonObject = new org.bson.types.Binary(type, data); } else if (jsMongoObj instanceof MinKey) { bsonObject = new org.bson.types.MinKey(); } else if (jsMongoObj instanceof MaxKey) { bsonObject = new org.bson.types.MaxKey(); } else if (jsMongoObj instanceof NumberInt) { bsonObject = Integer.valueOf(((NumberInt) jsMongoObj).getRealInt()); } else if (jsMongoObj instanceof NumberLong) { bsonObject = Long.valueOf(((NumberLong) jsMongoObj).getRealLong()); } else if (jsMongoObj instanceof DBRef) { DBRef jsRef = (DBRef) jsMongoObj; Object id = convertJStoBSON(jsRef.getId(), isJsObj, dateFormat); bsonObject = new com.mongodb.DBRef(jsRef.getNs(), id); } else if (jsMongoObj instanceof Timestamp) { bsonObject = convertTimestampToBSONTimestamp((Timestamp) jsMongoObj); } return bsonObject; } /** * seconds since epoch, used for Timestamp to BSONTimestamp conversion */ private static int lastSecFromEpoch; /** * ordinal used for Timestamp to BSONTimestamp conversion */ private static int timestampIncrementer = 1; private static synchronized BSONTimestamp convertTimestampToBSONTimestamp(Timestamp tstamp) { BSONTimestamp bsTstamp; int newTimeInSec = (int) tstamp.getT(); if (newTimeInSec == 0) { newTimeInSec = (int) (new Date().getTime() / 1000); // seconds from epoch has changed, reset ordinal and set the new // lastSecFromEpoch value if (newTimeInSec != lastSecFromEpoch) { lastSecFromEpoch = newTimeInSec; timestampIncrementer = 1; } else timestampIncrementer++; bsTstamp = new BSONTimestamp(lastSecFromEpoch, timestampIncrementer); } else bsTstamp = new BSONTimestamp(newTimeInSec, (int) tstamp.getI()); return bsTstamp; } private static class JSPopulatePropertyAction extends MongoAction { private Scriptable obj; private Object key; private Object value; public JSPopulatePropertyAction(Scriptable obj, Object key, Object value) { super(null); this.obj = obj; this.key = key; this.value = value; } @Override public Object doRun(Context cx) { return ScriptRuntime.setObjectElem(obj, key, value, cx); } } private static class JSDecompileAction extends MongoAction { private BaseFunction toDecompile; public JSDecompileAction(BaseFunction toDecompile) { super(null); this.toDecompile = toDecompile; } @Override public Object doRun(Context cx) { return cx.decompileFunction(toDecompile, 2); } } }