diff --git a/src/com/massivecraft/mcore3/MCore.java b/src/com/massivecraft/mcore3/MCore.java index c2dc2e7d..52745835 100644 --- a/src/com/massivecraft/mcore3/MCore.java +++ b/src/com/massivecraft/mcore3/MCore.java @@ -14,7 +14,9 @@ import org.bukkit.plugin.java.JavaPlugin; import com.massivecraft.mcore3.cmd.Cmd; import com.massivecraft.mcore3.gson.InventoryTypeAdapter; import com.massivecraft.mcore3.gson.ItemStackAdapter; +import com.massivecraft.mcore3.gson.MongoURIAdapter; import com.massivecraft.mcore3.lib.gson.GsonBuilder; +import com.massivecraft.mcore3.lib.mongodb.MongoURI; import com.massivecraft.mcore3.persist.One; import com.massivecraft.mcore3.persist.Persist; import com.massivecraft.mcore3.util.LibLoader; @@ -113,6 +115,7 @@ public class MCore extends JavaPlugin .setPrettyPrinting() .disableHtmlEscaping() .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .registerTypeAdapter(MongoURI.class, MongoURIAdapter.get()) .registerTypeAdapter(ItemStack.class, new ItemStackAdapter()) .registerTypeAdapter(Inventory.class, new InventoryTypeAdapter()); } diff --git a/src/com/massivecraft/mcore3/gson/MongoURIAdapter.java b/src/com/massivecraft/mcore3/gson/MongoURIAdapter.java new file mode 100644 index 00000000..c5aa1213 --- /dev/null +++ b/src/com/massivecraft/mcore3/gson/MongoURIAdapter.java @@ -0,0 +1,55 @@ +package com.massivecraft.mcore3.gson; + +import java.lang.reflect.Type; + +import com.massivecraft.mcore3.lib.gson.JsonDeserializationContext; +import com.massivecraft.mcore3.lib.gson.JsonDeserializer; +import com.massivecraft.mcore3.lib.gson.JsonElement; +import com.massivecraft.mcore3.lib.gson.JsonParseException; +import com.massivecraft.mcore3.lib.gson.JsonPrimitive; +import com.massivecraft.mcore3.lib.gson.JsonSerializationContext; +import com.massivecraft.mcore3.lib.gson.JsonSerializer; +import com.massivecraft.mcore3.lib.mongodb.MongoURI; + +public class MongoURIAdapter implements JsonDeserializer, JsonSerializer +{ + // -------------------------------------------- // + // IMPLEMENTATION + // -------------------------------------------- // + + @Override + public JsonElement serialize(MongoURI mongoURI, Type typeOfSrc, JsonSerializationContext context) + { + return serialize(mongoURI); + } + + @Override + public MongoURI deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException + { + return deserialize(json); + } + + // -------------------------------------------- // + // STATIC LOGIC + // -------------------------------------------- // + + public static JsonElement serialize(MongoURI mongoURI) + { + return new JsonPrimitive(mongoURI.toString()); + } + + public static MongoURI deserialize(JsonElement json) + { + return new MongoURI(json.getAsString()); + } + + // -------------------------------------------- // + // INSTANCE + // -------------------------------------------- // + + protected static MongoURIAdapter instance = new MongoURIAdapter(); + public static MongoURIAdapter get() + { + return instance; + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BSON.java b/src/com/massivecraft/mcore3/lib/bson/BSON.java new file mode 100644 index 00000000..4029ef3e --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSON.java @@ -0,0 +1,353 @@ +// BSON.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import com.massivecraft.mcore3.lib.bson.util.ClassMap; + +public class BSON { + + static final Logger LOGGER = Logger.getLogger( "org.bson.BSON" ); + + // ---- basics ---- + + public static final byte EOO = 0; + public static final byte NUMBER = 1; + public static final byte STRING = 2; + public static final byte OBJECT = 3; + public static final byte ARRAY = 4; + public static final byte BINARY = 5; + public static final byte UNDEFINED = 6; + public static final byte OID = 7; + public static final byte BOOLEAN = 8; + public static final byte DATE = 9; + public static final byte NULL = 10; + public static final byte REGEX = 11; + public static final byte REF = 12; + public static final byte CODE = 13; + public static final byte SYMBOL = 14; + public static final byte CODE_W_SCOPE = 15; + public static final byte NUMBER_INT = 16; + public static final byte TIMESTAMP = 17; + public static final byte NUMBER_LONG = 18; + + public static final byte MINKEY = -1; + public static final byte MAXKEY = 127; + + // --- binary types + /* + these are binary types + so the format would look like + <...> + */ + + public static final byte B_GENERAL = 0; + public static final byte B_FUNC = 1; + public static final byte B_BINARY = 2; + public static final byte B_UUID = 3; + + // ---- regular expression handling ---- + + /** Converts a string of regular expression flags from the database in Java regular + * expression flags. + * @param flags flags from database + * @return the Java flags + */ + public static int regexFlags( String flags ){ + int fint = 0; + if ( flags == null || flags.length() == 0 ) + return fint; + + flags = flags.toLowerCase(); + + for( int i=0; i 0 ) { + buf.append( flag.flagChar ); + flags -= flag.javaFlag; + } + } + + if( flags > 0 ) + throw new IllegalArgumentException( "some flags could not be recognized." ); + + return buf.toString(); + } + + private static enum RegexFlag { + CANON_EQ( Pattern.CANON_EQ, 'c', "Pattern.CANON_EQ" ), + UNIX_LINES(Pattern.UNIX_LINES, 'd', "Pattern.UNIX_LINES" ), + GLOBAL( GLOBAL_FLAG, 'g', null ), + CASE_INSENSITIVE( Pattern.CASE_INSENSITIVE, 'i', null ), + MULTILINE(Pattern.MULTILINE, 'm', null ), + DOTALL( Pattern.DOTALL, 's', "Pattern.DOTALL" ), + LITERAL( Pattern.LITERAL, 't', "Pattern.LITERAL" ), + UNICODE_CASE( Pattern.UNICODE_CASE, 'u', "Pattern.UNICODE_CASE" ), + COMMENTS( Pattern.COMMENTS, 'x', null ); + + private static final Map byCharacter = new HashMap(); + + static { + for (RegexFlag flag : values()) { + byCharacter.put(flag.flagChar, flag); + } + } + + public static RegexFlag getByCharacter(char ch) { + return byCharacter.get(ch); + } + public final int javaFlag; + public final char flagChar; + public final String unsupported; + + RegexFlag( int f, char ch, String u ) { + javaFlag = f; + flagChar = ch; + unsupported = u; + } + } + + private static void _warnUnsupportedRegex( String flag ) { + LOGGER.info( "flag " + flag + " not supported by db." ); + } + + private static final int GLOBAL_FLAG = 256; + + // --- (en|de)coding hooks ----- + + public static boolean hasDecodeHooks() { return _decodeHooks; } + + @SuppressWarnings("rawtypes") + public static void addEncodingHook( Class c , Transformer t ){ + _encodeHooks = true; + List l = _encodingHooks.get( c ); + if ( l == null ){ + l = new CopyOnWriteArrayList(); + _encodingHooks.put( c , l ); + } + l.add( t ); + } + + @SuppressWarnings("rawtypes") + public static void addDecodingHook( Class c , Transformer t ){ + _decodeHooks = true; + List l = _decodingHooks.get( c ); + if ( l == null ){ + l = new CopyOnWriteArrayList(); + _decodingHooks.put( c , l ); + } + l.add( t ); + } + + public static Object applyEncodingHooks( Object o ){ + if ( ! _anyHooks() ) + return o; + + if ( _encodingHooks.size() == 0 || o == null ) + return o; + List l = _encodingHooks.get( o.getClass() ); + if ( l != null ) + for ( Transformer t : l ) + o = t.transform( o ); + return o; + } + + public static Object applyDecodingHooks( Object o ){ + if ( ! _anyHooks() || o == null ) + return o; + + List l = _decodingHooks.get( o.getClass() ); + if ( l != null ) + for ( Transformer t : l ) + o = t.transform( o ); + return o; + } + + /** + * Returns the encoding hook(s) associated with the specified class + * + */ + @SuppressWarnings("rawtypes") + public static List getEncodingHooks( Class c ){ + return _encodingHooks.get( c ); + } + + /** + * Clears *all* encoding hooks. + */ + public static void clearEncodingHooks(){ + _encodeHooks = false; + _encodingHooks.clear(); + } + + /** + * Remove all encoding hooks for a specific class. + */ + @SuppressWarnings("rawtypes") + public static void removeEncodingHooks( Class c ){ + _encodingHooks.remove( c ); + } + + /** + * Remove a specific encoding hook for a specific class. + */ + @SuppressWarnings("rawtypes") + public static void removeEncodingHook( Class c , Transformer t ){ + getEncodingHooks( c ).remove( t ); + } + + /** + * Returns the decoding hook(s) associated with the specific class + */ + @SuppressWarnings("rawtypes") + public static List getDecodingHooks( Class c ){ + return _decodingHooks.get( c ); + } + + /** + * Clears *all* decoding hooks. + */ + public static void clearDecodingHooks(){ + _decodeHooks = false; + _decodingHooks.clear(); + } + + /** + * Remove all decoding hooks for a specific class. + */ + @SuppressWarnings("rawtypes") + public static void removeDecodingHooks( Class c ){ + _decodingHooks.remove( c ); + } + + /** + * Remove a specific encoding hook for a specific class. + */ + @SuppressWarnings("rawtypes") + public static void removeDecodingHook( Class c , Transformer t ){ + getDecodingHooks( c ).remove( t ); + } + + + public static void clearAllHooks(){ + clearEncodingHooks(); + clearDecodingHooks(); + } + + /** + * Returns true if any encoding or decoding hooks are loaded. + */ + private static boolean _anyHooks(){ + return _encodeHooks || _decodeHooks; + } + + private static boolean _encodeHooks = false; + private static boolean _decodeHooks = false; + static ClassMap> _encodingHooks = + new ClassMap>(); + + static ClassMap> _decodingHooks = + new ClassMap>(); + + static protected Charset _utf8 = Charset.forName( "UTF-8" ); + + // ----- static encode/decode ----- + + public static byte[] encode( BSONObject o ){ + BSONEncoder e = _staticEncoder.get(); + try { + return e.encode( o ); + } + finally { + e.done(); + } + } + + public static BSONObject decode( byte[] b ){ + BSONDecoder d = _staticDecoder.get(); + return d.readObject( b ); + } + + static ThreadLocal _staticEncoder = new ThreadLocal(){ + protected BSONEncoder initialValue(){ + return new BasicBSONEncoder(); + } + }; + + static ThreadLocal _staticDecoder = new ThreadLocal(){ + protected BSONDecoder initialValue(){ + return new BasicBSONDecoder(); + } + }; + + // --- coercing --- + + public static int toInt( Object o ){ + if ( o == null ) + throw new NullPointerException( "can't be null" ); + + if ( o instanceof Number ) + return ((Number)o).intValue(); + + if ( o instanceof Boolean ) + return ((Boolean)o) ? 1 : 0; + + throw new IllegalArgumentException( "can't convert: " + o.getClass().getName() + " to int" ); + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/BSONCallback.java new file mode 100644 index 00000000..b4e9ef48 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSONCallback.java @@ -0,0 +1,70 @@ +// BSONCallback.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +public interface BSONCallback { + + void objectStart(); + void objectStart(String name); + void objectStart(boolean array); + Object objectDone(); + + void reset(); + Object get(); + BSONCallback createBSONCallback(); + + void arrayStart(); + void arrayStart(String name); + Object arrayDone(); + + void gotNull( String name ); + void gotUndefined( String name ); + void gotMinKey( String name ); + void gotMaxKey( String name ); + + void gotBoolean( String name , boolean v ); + void gotDouble( String name , double v ); + void gotInt( String name , int v ); + void gotLong( String name , long v ); + + void gotDate( String name , long millis ); + void gotString( String name , String v ); + void gotSymbol( String name , String v ); + void gotRegex( String name , String pattern , String flags ); + + void gotTimestamp( String name , int time , int inc ); + void gotObjectId( String name , ObjectId id ); + void gotDBRef( String name , String ns , ObjectId id ); + + /** + * + */ + @Deprecated + void gotBinaryArray( String name , byte[] data ); + void gotBinary( String name , byte type , byte[] data ); + /** + * subtype 3 + */ + void gotUUID( String name , long part1, long part2); + + void gotCode( String name , String code ); + void gotCodeWScope( String name , String code , Object scope ); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java new file mode 100644 index 00000000..795ab63b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java @@ -0,0 +1,34 @@ +// BSONDecoder.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import java.io.IOException; +import java.io.InputStream; + +public interface BSONDecoder { + + public BSONObject readObject( byte[] b ); + + public BSONObject readObject( InputStream in ) throws IOException; + + public int decode( byte[] b , BSONCallback callback ); + + public int decode( InputStream in , BSONCallback callback ) throws IOException; + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java b/src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java new file mode 100644 index 00000000..d6cd1228 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2008 - 2011 10gen, Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.io.*; + + +public interface BSONEncoder { + public byte[] encode( BSONObject o ); + + public int putObject( BSONObject o ); + + public void done(); + + void set( OutputBuffer out ); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONException.java b/src/com/massivecraft/mcore3/lib/bson/BSONException.java new file mode 100644 index 00000000..a9517333 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSONException.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2011, 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +/** + * A general runtime exception raised in BSON processing. + */ +public class BSONException extends RuntimeException { + + private static final long serialVersionUID = -4415279469780082174L; + + /** + * @param msg The error message. + */ + public BSONException( final String msg ) { + super( msg ); + } + + /** + * @param errorCode The error code. + * @param msg The error message. + */ + public BSONException( final int errorCode, final String msg ) { + super( msg ); + _errorCode = errorCode; + } + + /** + * @param msg The error message. + * @param t The throwable cause. + */ + public BSONException( final String msg , final Throwable t ) { + super( msg, t ); + } + + /** + * @param errorCode The error code. + * @param msg The error message. + * @param t The throwable cause. + */ + public BSONException( final int errorCode, final String msg, final Throwable t ) { + super( msg, t ); + _errorCode = errorCode; + } + + /** + * Returns the error code. + * @return The error code. + */ + public Integer getErrorCode() { return _errorCode; } + + /** + * Returns true if the error code is set (i.e., not null). + */ + public boolean hasErrorCode() { return (_errorCode != null); } + + private Integer _errorCode = null; +} + diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java b/src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java new file mode 100644 index 00000000..09882463 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +/** + * + * @author antoine + */ +public class BSONLazyDecoder { + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONObject.java b/src/com/massivecraft/mcore3/lib/bson/BSONObject.java new file mode 100644 index 00000000..95b376f1 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BSONObject.java @@ -0,0 +1,93 @@ +// BSONObject.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import java.util.Map; +import java.util.Set; + +/** + * A key-value map that can be saved to the database. + */ +public interface BSONObject { + + /** + * Sets a name/value pair in this object. + * @param key Name to set + * @param v Corresponding value + * @return v + */ + public Object put( String key , Object v ); + + /** + * Sets all key/value pairs from an object into this object + * @param o the object + */ + public void putAll( BSONObject o ); + + /** + * Sets all key/value pairs from a map into this object + * @param m the map + */ + @SuppressWarnings("rawtypes") + public void putAll( Map m ); + + /** + * Gets a field from this object by a given name. + * @param key The name of the field fetch + * @return The field, if found + */ + public Object get( String key ); + + /** + * Returns a map representing this BSONObject. + * @return the map + */ + @SuppressWarnings("rawtypes") + public Map toMap(); + + /** + * Removes a field with a given name from this object. + * @param key The name of the field to remove + * @return The value removed from this object + */ + public Object removeField( String key ); + + /** + * Deprecated + * @param s + * @return True if the key is present + * @deprecated + */ + @Deprecated + public boolean containsKey( String s ); + + /** + * Checks if this object contains a field with the given name. + * @param s Field name for which to check + * @return True if the field is present + */ + public boolean containsField(String s); + + /** + * Returns this object's fields' names + * @return The names of the fields in this object + */ + public Set keySet(); +} + diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java new file mode 100644 index 00000000..3419f79a --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java @@ -0,0 +1,206 @@ +// BasicBSONCallback.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import java.util.*; +import java.util.regex.Pattern; + +import com.massivecraft.mcore3.lib.bson.types.*; + +public class BasicBSONCallback implements BSONCallback { + + public BasicBSONCallback(){ + reset(); + } + + public BSONObject create(){ + return new BasicBSONObject(); + } + + protected BSONObject createList() { + return new BasicBSONList(); + } + + public BSONCallback createBSONCallback(){ + return new BasicBSONCallback(); + } + + public BSONObject create( boolean array , List path ){ + if ( array ) + return createList(); + return create(); + } + + public void objectStart(){ + if ( _stack.size() > 0 ) + throw new IllegalStateException( "something is wrong" ); + + objectStart(false); + } + + public void objectStart(boolean array){ + _root = create(array, null); + _stack.add( (BSONObject)_root ); + } + + public void objectStart(String name){ + objectStart( false , name ); + } + + public void objectStart(boolean array, String name){ + _nameStack.addLast( name ); + final BSONObject o = create( array , _nameStack ); + _stack.getLast().put( name , o); + _stack.addLast( o ); + } + + public Object objectDone(){ + final BSONObject o =_stack.removeLast(); + if ( _nameStack.size() > 0 ) + _nameStack.removeLast(); + else if ( _stack.size() > 0 ) + throw new IllegalStateException( "something is wrong" ); + + return !BSON.hasDecodeHooks() ? o : (BSONObject)BSON.applyDecodingHooks(o); + } + + public void arrayStart(){ + objectStart( true ); + } + + public void arrayStart(String name){ + objectStart( true , name ); + } + + public Object arrayDone(){ + return objectDone(); + } + + public void gotNull( String name ){ + cur().put( name , null ); + } + + public void gotUndefined( String name ) { } + + public void gotMinKey( String name ){ + cur().put( name , new MinKey() ); + } + + public void gotMaxKey( String name ){ + cur().put( name , new MaxKey() ); + } + + public void gotBoolean( String name , boolean v ){ + _put( name , v ); + } + + public void gotDouble( final String name , final double v ){ + _put( name , v ); + } + + public void gotInt( final String name , final int v ){ + _put( name , v ); + } + + public void gotLong( final String name , final long v ){ + _put( name , v ); + } + + public void gotDate( String name , long millis ){ + _put( name , new Date( millis ) ); + } + public void gotRegex( String name , String pattern , String flags ){ + _put( name , Pattern.compile( pattern , BSON.regexFlags( flags ) ) ); + } + + public void gotString( final String name , final String v ){ + _put( name , v ); + } + public void gotSymbol( String name , String v ){ + _put( name , v ); + } + + public void gotTimestamp( String name , int time , int inc ){ + _put( name , new BSONTimestamp( time , inc ) ); + } + public void gotObjectId( String name , ObjectId id ){ + _put( name , id ); + } + public void gotDBRef( String name , String ns , ObjectId id ){ + _put( name , new BasicBSONObject( "$ns" , ns ).append( "$id" , id ) ); + } + + @Deprecated + public void gotBinaryArray( String name , byte[] data ){ + gotBinary( name, BSON.B_GENERAL, data ); + } + + public void gotBinary( String name , byte type , byte[] data ){ + if( type == BSON.B_GENERAL || type == BSON.B_BINARY ) + _put( name , data ); + else + _put( name , new Binary( type , data ) ); + } + + public void gotUUID( String name , long part1, long part2){ + _put( name , new UUID(part1, part2) ); + } + + public void gotCode( String name , String code ){ + _put( name , new Code( code ) ); + } + + public void gotCodeWScope( String name , String code , Object scope ){ + _put( name , new CodeWScope( code, (BSONObject)scope ) ); + } + + protected void _put( final String name , final Object o ){ + cur().put( name , !BSON.hasDecodeHooks() ? o : BSON.applyDecodingHooks( o ) ); + } + + protected BSONObject cur(){ + return _stack.getLast(); + } + + protected String curName(){ + return (!_nameStack.isEmpty()) ? _nameStack.getLast() : null; + } + + public Object get(){ + return _root; + } + + protected void setRoot(Object o) { + _root = o; + } + + protected boolean isStackEmpty() { + return _stack.size() < 1; + } + + public void reset(){ + _root = null; + _stack.clear(); + _nameStack.clear(); + } + + private Object _root; + private final LinkedList _stack = new LinkedList(); + private final LinkedList _nameStack = new LinkedList(); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java new file mode 100644 index 00000000..7a5429a0 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java @@ -0,0 +1,527 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import static com.massivecraft.mcore3.lib.bson.BSON.*; + +import java.io.*; + +import com.massivecraft.mcore3.lib.bson.io.PoolOutputBuffer; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + + +/** + * Basic implementation of BSONDecoder interface that creates BasicBSONObject instances + */ +public class BasicBSONDecoder implements BSONDecoder { + public BSONObject readObject( byte[] b ){ + try { + return readObject( new ByteArrayInputStream( b ) ); + } + catch ( IOException ioe ){ + throw new BSONException( "should be impossible" , ioe ); + } + } + + public BSONObject readObject( InputStream in ) + throws IOException { + BasicBSONCallback c = new BasicBSONCallback(); + decode( in , c ); + return (BSONObject)c.get(); + } + + public int decode( byte[] b , BSONCallback callback ){ + try { + return _decode( new BSONInput( new ByteArrayInputStream(b) ) , callback ); + } + catch ( IOException ioe ){ + throw new BSONException( "should be impossible" , ioe ); + } + } + + public int decode( InputStream in , BSONCallback callback ) + throws IOException { + return _decode( new BSONInput( in ) , callback ); + } + + private int _decode( BSONInput in , BSONCallback callback ) + throws IOException { + + if ( _in != null || _callback != null ) + throw new IllegalStateException( "not ready" ); + + _in = in; + _callback = callback; + + if ( in.numRead() != 0 ) + throw new IllegalArgumentException( "i'm confused" ); + + try { + + final int len = _in.readInt(); + + _in.setMax(len); + + _callback.objectStart(); + while ( decodeElement() ); + _callback.objectDone(); + + if ( _in.numRead() != len ) + throw new IllegalArgumentException( "bad data. lengths don't match read:" + _in.numRead() + " != len:" + len ); + + return len; + } + finally { + _in = null; + _callback = null; + } + } + + int decode( boolean first ) + throws IOException { + + final int start = _in.numRead(); + + final int len = _in.readInt(); + if ( first ) + _in.setMax(len); + + _callback.objectStart(); + while ( decodeElement() ); + _callback.objectDone(); + + final int read = _in.numRead() - start; + + if ( read != len ){ + //throw new IllegalArgumentException( "bad data. lengths don't match " + read + " != " + len ); + } + + return len; + } + + boolean decodeElement() + throws IOException { + + final byte type = _in.read(); + + if ( type == EOO ) + return false; + + String name = _in.readCStr(); + + switch ( type ){ + case NULL: + _callback.gotNull( name ); + break; + + case UNDEFINED: + _callback.gotUndefined( name ); + break; + + case BOOLEAN: + _callback.gotBoolean( name , _in.read() > 0 ); + break; + + case NUMBER: + _callback.gotDouble( name , _in.readDouble() ); + break; + + case NUMBER_INT: + _callback.gotInt( name , _in.readInt() ); + break; + + case NUMBER_LONG: + _callback.gotLong( name , _in.readLong() ); + break; + + case SYMBOL: + _callback.gotSymbol( name , _in.readUTF8String() ); + break; + + case STRING: + _callback.gotString(name, _in.readUTF8String() ); + break; + + case OID: + // OID is stored as big endian + _callback.gotObjectId( name , new ObjectId( _in.readIntBE() , _in.readIntBE() , _in.readIntBE() ) ); + break; + + case REF: + _in.readInt(); // length of ctring that follows + String ns = _in.readCStr(); + ObjectId theOID = new ObjectId( _in.readInt() , _in.readInt() , _in.readInt() ); + _callback.gotDBRef( name , ns , theOID ); + break; + + case DATE: + _callback.gotDate( name , _in.readLong() ); + break; + + case REGEX: + _callback.gotRegex( name , _in.readCStr() , _in.readCStr() ); + break; + + case BINARY: + _binary( name ); + break; + + case CODE: + _callback.gotCode( name , _in.readUTF8String() ); + break; + + case CODE_W_SCOPE: + _in.readInt(); + _callback.gotCodeWScope( name , _in.readUTF8String() , _readBasicObject() ); + + break; + + case ARRAY: + _in.readInt(); // total size - we don't care.... + + _callback.arrayStart( name ); + while ( decodeElement() ); + _callback.arrayDone(); + + break; + + + case OBJECT: + _in.readInt(); // total size - we don't care.... + + _callback.objectStart( name ); + while ( decodeElement() ); + _callback.objectDone(); + + break; + + case TIMESTAMP: + int i = _in.readInt(); + int time = _in.readInt(); + _callback.gotTimestamp( name , time , i ); + break; + + case MINKEY: + _callback.gotMinKey( name ); + break; + + case MAXKEY: + _callback.gotMaxKey( name ); + break; + + default: + throw new UnsupportedOperationException( "BSONDecoder doesn't understand type : " + type + " name: " + name ); + } + + return true; + } + + protected void _binary( final String name ) + throws IOException { + final int totalLen = _in.readInt(); + final byte bType = _in.read(); + + switch ( bType ){ + case B_GENERAL: { + final byte[] data = new byte[totalLen]; + _in.fill( data ); + _callback.gotBinary( name, bType, data ); + return; + } + case B_BINARY: + final int len = _in.readInt(); + if ( len + 4 != totalLen ) + throw new IllegalArgumentException( "bad data size subtype 2 len: " + len + " totalLen: " + totalLen ); + + final byte [] data = new byte[len]; + _in.fill( data ); + _callback.gotBinary( name , bType , data ); + return; + case B_UUID: + if ( totalLen != 16 ) + throw new IllegalArgumentException( "bad data size subtype 3 len: " + totalLen + " != 16"); + + final long part1 = _in.readLong(); + final long part2 = _in.readLong(); + _callback.gotUUID(name, part1, part2); + return; + } + + final byte [] data = new byte[totalLen]; + _in.fill( data ); + + _callback.gotBinary( name , bType , data ); + } + + Object _readBasicObject() + throws IOException { + _in.readInt(); + + final BSONCallback save = _callback; + final BSONCallback _basic = _callback.createBSONCallback(); + _callback = _basic; + _basic.reset(); + _basic.objectStart(false); + + while( decodeElement() ); + _callback = save; + return _basic.get(); + } + + protected class BSONInput { + + public BSONInput(final InputStream in){ + _raw = in; + _read = 0; + + _pos = 0; + _len = 0; + } + + /** + * ensure that there are num bytes to read + * _pos is where to start reading from + * @return where to start reading from + */ + protected int _need( final int num ) + throws IOException { + + //System.out.println( "p: " + _pos + " l: " + _len + " want: " + num ); + + if ( _len - _pos >= num ){ + final int ret = _pos; + _pos += num; + _read += num; + return ret; + } + + if ( num >= _inputBuffer.length ) + throw new IllegalArgumentException( "you can't need that much" ); + + final int remaining = _len - _pos; + if ( _pos > 0 ){ + System.arraycopy( _inputBuffer , _pos , _inputBuffer , 0 , remaining ); + + _pos = 0; + _len = remaining; + } + + // read as much as possible into buffer + int maxToRead = Math.min( _max - _read - remaining , _inputBuffer.length - _len ); + while ( maxToRead > 0 ){ + int x = _raw.read( _inputBuffer , _len , maxToRead); + if ( x <= 0 ) + throw new IOException( "unexpected EOF" ); + maxToRead -= x; + _len += x; + } + + int ret = _pos; + _pos += num; + _read += num; + return ret; + } + + public int readInt() + throws IOException { + return com.massivecraft.mcore3.lib.bson.io.Bits.readInt( _inputBuffer , _need(4) ); + } + + public int readIntBE() + throws IOException { + return com.massivecraft.mcore3.lib.bson.io.Bits.readIntBE( _inputBuffer , _need(4) ); + } + + public long readLong() + throws IOException { + return com.massivecraft.mcore3.lib.bson.io.Bits.readLong( _inputBuffer , _need(8) ); + } + + public double readDouble() + throws IOException { + return Double.longBitsToDouble( readLong() ); + } + + public byte read() + throws IOException { + if ( _pos < _len ){ + ++_read; + return _inputBuffer[_pos++]; + } + return _inputBuffer[_need(1)]; + } + + public void fill( byte b[] ) + throws IOException { + fill( b , b.length ); + } + + public void fill( byte b[] , int len ) + throws IOException { + // first use what we have + final int have = _len - _pos; + final int tocopy = Math.min( len , have ); + System.arraycopy( _inputBuffer , _pos , b , 0 , tocopy ); + + _pos += tocopy; + _read += tocopy; + + len -= tocopy; + + int off = tocopy; + while ( len > 0 ){ + final int x = _raw.read( b , off , len ); + if (x <= 0) + throw new IOException( "unexpected EOF" ); + _read += x; + off += x; + len -= x; + } + } + + protected boolean _isAscii( byte b ){ + return b >=0 && b <= 127; + } + + public String readCStr() throws IOException { + + boolean isAscii = true; + + // short circuit 1 byte strings + _random[0] = read(); + if (_random[0] == 0) { + return ""; + } + + _random[1] = read(); + if (_random[1] == 0) { + final String out = ONE_BYTE_STRINGS[_random[0]]; + return (out != null) ? out : new String(_random, 0, 1, DEFAULT_ENCODING); + } + + _stringBuffer.reset(); + _stringBuffer.write(_random, 0, 2); + + isAscii = _isAscii(_random[0]) && _isAscii(_random[1]); + + byte b; + while ((b = read()) != 0) { + _stringBuffer.write( b ); + isAscii = isAscii && _isAscii( b ); + } + + String out = null; + if ( isAscii ){ + out = _stringBuffer.asAscii(); + } + else { + try { + out = _stringBuffer.asString( DEFAULT_ENCODING ); + } + catch ( UnsupportedOperationException e ){ + throw new BSONException( "impossible" , e ); + } + } + _stringBuffer.reset(); + return out; + } + + public String readUTF8String() + throws IOException { + final int size = readInt(); + // this is just protection in case it's corrupted, to avoid huge strings + if ( size <= 0 || size > MAX_STRING ) + throw new BSONException( "bad string size: " + size ); + + if ( size < _inputBuffer.length / 2 ){ + if ( size == 1 ){ + read(); + return ""; + } + + return new String( _inputBuffer , _need(size) , size - 1 , DEFAULT_ENCODING ); + } + + final byte [] b = size < _random.length ? _random : new byte[size]; + + fill( b , size ); + + try { + return new String( b , 0 , size - 1 , DEFAULT_ENCODING ); + } + catch ( java.io.UnsupportedEncodingException uee ){ + throw new BSONException( "impossible" , uee ); + } + } + + public int numRead() { + return _read; + } + + public int getPos() { + return _pos; + } + + public int getMax() { + return _max; + } + + public void setMax(int _max) { + this._max = _max; + } + + int _read; + final InputStream _raw; + + int _max = 4; // max number of total bytes allowed to ready + + } + + protected BSONInput _in; + protected BSONCallback _callback; + + private byte [] _random = new byte[1024]; // has to be used within a single function + private byte [] _inputBuffer = new byte[1024]; + + private PoolOutputBuffer _stringBuffer = new PoolOutputBuffer(); + + protected int _pos; // current offset into _inputBuffer + protected int _len; // length of valid data in _inputBuffer + + private static final int MAX_STRING = ( 32 * 1024 * 1024 ); + + private static final String DEFAULT_ENCODING = "UTF-8"; + + @SuppressWarnings("unused") + private static final boolean _isAscii( final byte b ){ + return b >=0 && b <= 127; + } + + static final String[] ONE_BYTE_STRINGS = new String[128]; + static void _fillRange( byte min, byte max ){ + while ( min < max ){ + String s = ""; + s += (char)min; + ONE_BYTE_STRINGS[(int)min] = s; + min++; + } + } + static { + _fillRange( (byte)'0' , (byte)'9' ); + _fillRange( (byte)'a' , (byte)'z' ); + _fillRange( (byte)'A' , (byte)'Z' ); + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java new file mode 100644 index 00000000..23e81975 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java @@ -0,0 +1,530 @@ +// BSONEncoder.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import static com.massivecraft.mcore3.lib.bson.BSON.ARRAY; +import static com.massivecraft.mcore3.lib.bson.BSON.BINARY; +import static com.massivecraft.mcore3.lib.bson.BSON.BOOLEAN; +import static com.massivecraft.mcore3.lib.bson.BSON.B_BINARY; +import static com.massivecraft.mcore3.lib.bson.BSON.B_GENERAL; +import static com.massivecraft.mcore3.lib.bson.BSON.B_UUID; +import static com.massivecraft.mcore3.lib.bson.BSON.CODE; +import static com.massivecraft.mcore3.lib.bson.BSON.CODE_W_SCOPE; +import static com.massivecraft.mcore3.lib.bson.BSON.DATE; +import static com.massivecraft.mcore3.lib.bson.BSON.EOO; +import static com.massivecraft.mcore3.lib.bson.BSON.MAXKEY; +import static com.massivecraft.mcore3.lib.bson.BSON.MINKEY; +import static com.massivecraft.mcore3.lib.bson.BSON.NULL; +import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER; +import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER_INT; +import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER_LONG; +import static com.massivecraft.mcore3.lib.bson.BSON.OBJECT; +import static com.massivecraft.mcore3.lib.bson.BSON.OID; +import static com.massivecraft.mcore3.lib.bson.BSON.REGEX; +import static com.massivecraft.mcore3.lib.bson.BSON.STRING; +import static com.massivecraft.mcore3.lib.bson.BSON.SYMBOL; +import static com.massivecraft.mcore3.lib.bson.BSON.TIMESTAMP; +import static com.massivecraft.mcore3.lib.bson.BSON.UNDEFINED; +import static com.massivecraft.mcore3.lib.bson.BSON.regexFlags; + +import java.lang.reflect.Array; +import java.nio.Buffer; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.Pattern; + + +import com.massivecraft.mcore3.lib.bson.io.BasicOutputBuffer; +import com.massivecraft.mcore3.lib.bson.io.OutputBuffer; +import com.massivecraft.mcore3.lib.bson.types.BSONTimestamp; +import com.massivecraft.mcore3.lib.bson.types.Binary; +import com.massivecraft.mcore3.lib.bson.types.Code; +import com.massivecraft.mcore3.lib.bson.types.CodeWScope; +import com.massivecraft.mcore3.lib.bson.types.MaxKey; +import com.massivecraft.mcore3.lib.bson.types.MinKey; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; +import com.massivecraft.mcore3.lib.bson.types.Symbol; +import com.massivecraft.mcore3.lib.mongodb.DBRefBase; + +/** + * this is meant to be pooled or cached + * there is some per instance memory for string conversion, etc... + */ +@SuppressWarnings("unchecked") +public class BasicBSONEncoder implements BSONEncoder { + + static final boolean DEBUG = false; + + public BasicBSONEncoder(){ + + } + + public byte[] encode( BSONObject o ){ + BasicOutputBuffer buf = new BasicOutputBuffer(); + set( buf ); + putObject( o ); + done(); + return buf.toByteArray(); + } + + public void set( OutputBuffer out ){ + if ( _buf != null ) + throw new IllegalStateException( "in the middle of something" ); + + _buf = out; + } + + public void done(){ + _buf = null; + } + + /** + * @return true if object was handled + */ + protected boolean handleSpecialObjects( String name , BSONObject o ){ + return false; + } + + protected boolean putSpecial( String name , Object o ){ + return false; + } + + /** Encodes a BSONObject. + * This is for the higher level api calls + * @param o the object to encode + * @return the number of characters in the encoding + */ + public int putObject( BSONObject o ){ + return putObject( null , o ); + } + + /** + * this is really for embedded objects + */ + @SuppressWarnings("rawtypes") + protected int putObject( String name , BSONObject o ){ + + if ( o == null ) + throw new NullPointerException( "can't save a null object" ); + + if ( DEBUG ) System.out.println( "putObject : " + name + " [" + o.getClass() + "]" + " # keys " + o.keySet().size() ); + + final int start = _buf.getPosition(); + + byte myType = OBJECT; + if ( o instanceof List ) + myType = ARRAY; + + if ( handleSpecialObjects( name , o ) ) + return _buf.getPosition() - start; + + if ( name != null ){ + _put( myType , name ); + } + + final int sizePos = _buf.getPosition(); + _buf.writeInt( 0 ); // leaving space for this. set it at the end + + List transientFields = null; + boolean rewriteID = myType == OBJECT && name == null; + + + if ( myType == OBJECT ) { + if ( rewriteID && o.containsField( "_id" ) ) + _putObjectField( "_id" , o.get( "_id" ) ); + + { + Object temp = o.get( "_transientFields" ); + if ( temp instanceof List ) + transientFields = (List)temp; + } + } + + //TODO: reduce repeated code below. + if ( o instanceof Map ){ + for ( Entry e : ((Map)o).entrySet() ){ + + if ( rewriteID && e.getKey().equals( "_id" ) ) + continue; + + if ( transientFields != null && transientFields.contains( e.getKey() ) ) + continue; + + _putObjectField( e.getKey() , e.getValue() ); + + } + } else { + for ( String s : o.keySet() ){ + + if ( rewriteID && s.equals( "_id" ) ) + continue; + + if ( transientFields != null && transientFields.contains( s ) ) + continue; + + Object val = o.get( s ); + + _putObjectField( s , val ); + + } + } + _buf.write( EOO ); + + _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); + return _buf.getPosition() - start; + } + + @SuppressWarnings("rawtypes") + protected void _putObjectField( String name , Object val ){ + + if ( name.equals( "_transientFields" ) ) + return; + + if ( DEBUG ) System.out.println( "\t put thing : " + name ); + + if ( name.equals( "$where") && val instanceof String ){ + _put( CODE , name ); + _putValueString( val.toString() ); + return; + } + + val = BSON.applyEncodingHooks( val ); + + if ( val == null ) + putNull(name); + else if ( val instanceof Date ) + putDate( name , (Date)val ); + else if ( val instanceof Number ) + putNumber(name, (Number)val ); + else if ( val instanceof Character ) + putString(name, val.toString() ); + else if ( val instanceof String ) + putString(name, val.toString() ); + else if ( val instanceof ObjectId ) + putObjectId(name, (ObjectId)val ); + else if ( val instanceof BSONObject ) + putObject(name, (BSONObject)val ); + else if ( val instanceof Boolean ) + putBoolean(name, (Boolean)val ); + else if ( val instanceof Pattern ) + putPattern(name, (Pattern)val ); + else if ( val instanceof Map ) + putMap( name , (Map)val ); + else if ( val instanceof Iterable) + putIterable( name , (Iterable)val ); + else if ( val instanceof byte[] ) + putBinary( name , (byte[])val ); + else if ( val instanceof Binary ) + putBinary( name , (Binary)val ); + else if ( val instanceof UUID ) + putUUID( name , (UUID)val ); + else if ( val.getClass().isArray() ) + putArray( name , val ); + + else if (val instanceof Symbol) { + putSymbol(name, (Symbol) val); + } + else if (val instanceof BSONTimestamp) { + putTimestamp( name , (BSONTimestamp)val ); + } + else if (val instanceof CodeWScope) { + putCodeWScope( name , (CodeWScope)val ); + } + else if (val instanceof Code) { + putCode( name , (Code)val ); + } + else if (val instanceof DBRefBase) { + BSONObject temp = new BasicBSONObject(); + temp.put("$ref", ((DBRefBase)val).getRef()); + temp.put("$id", ((DBRefBase)val).getId()); + putObject( name, temp ); + } + else if ( val instanceof MinKey ) + putMinKey( name ); + else if ( val instanceof MaxKey ) + putMaxKey( name ); + else if ( putSpecial( name , val ) ){ + // no-op + } + else { + throw new IllegalArgumentException( "can't serialize " + val.getClass() ); + } + + } + + private void putArray( String name , Object array ) { + _put( ARRAY , name ); + final int sizePos = _buf.getPosition(); + _buf.writeInt( 0 ); + + int size = Array.getLength(array); + for ( int i = 0; i < size; i++ ) + _putObjectField( String.valueOf( i ) , Array.get( array, i ) ); + + _buf.write( EOO ); + _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); + } + + @SuppressWarnings("rawtypes") + private void putIterable( String name , Iterable l ){ + _put( ARRAY , name ); + final int sizePos = _buf.getPosition(); + _buf.writeInt( 0 ); + + int i=0; + for ( Object obj: l ) { + _putObjectField( String.valueOf( i ) , obj ); + i++; + } + + + _buf.write( EOO ); + _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); + } + + @SuppressWarnings("rawtypes") + private void putMap( String name , Map m ){ + _put( OBJECT , name ); + final int sizePos = _buf.getPosition(); + _buf.writeInt( 0 ); + + for ( Map.Entry entry : (Set)m.entrySet() ) + _putObjectField( entry.getKey().toString() , entry.getValue() ); + + _buf.write( EOO ); + _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); + } + + + protected void putNull( String name ){ + _put( NULL , name ); + } + + protected void putUndefined(String name){ + _put(UNDEFINED, name); + } + + protected void putTimestamp(String name, BSONTimestamp ts ){ + _put( TIMESTAMP , name ); + _buf.writeInt( ts.getInc() ); + _buf.writeInt( ts.getTime() ); + } + + protected void putCodeWScope( String name , CodeWScope code ){ + _put( CODE_W_SCOPE , name ); + int temp = _buf.getPosition(); + _buf.writeInt( 0 ); + _putValueString( code.getCode() ); + putObject( code.getScope() ); + _buf.writeInt( temp , _buf.getPosition() - temp ); + } + + @SuppressWarnings("unused") + protected void putCode( String name , Code code ){ + _put( CODE , name ); + int temp = _buf.getPosition(); + _putValueString( code.getCode() ); + } + + protected void putBoolean( String name , Boolean b ){ + _put( BOOLEAN , name ); + _buf.write( b ? (byte)0x1 : (byte)0x0 ); + } + + protected void putDate( String name , Date d ){ + _put( DATE , name ); + _buf.writeLong( d.getTime() ); + } + + protected void putNumber( String name , Number n ){ + if ( n instanceof Integer || + n instanceof Short || + n instanceof Byte || + n instanceof AtomicInteger ){ + _put( NUMBER_INT , name ); + _buf.writeInt( n.intValue() ); + } + else if ( n instanceof Long || n instanceof AtomicLong ) { + _put( NUMBER_LONG , name ); + _buf.writeLong( n.longValue() ); + } + else if ( n instanceof Float || n instanceof Double ) { + _put( NUMBER , name ); + _buf.writeDouble( n.doubleValue() ); + } + else { + throw new IllegalArgumentException( "can't serialize " + n.getClass() ); + } + } + + protected void putBinary( String name , byte[] data ){ + putBinary( name, B_GENERAL, data ); + } + + protected void putBinary( String name , Binary val ){ + putBinary( name, val.getType(), val.getData() ); + } + + private void putBinary( String name , int type , byte[] data ){ + _put( BINARY , name ); + int totalLen = data.length; + + if (type == B_BINARY) + totalLen += 4; + + _buf.writeInt( totalLen ); + _buf.write( type ); + if (type == B_BINARY) + _buf.writeInt( totalLen -4 ); + int before = _buf.getPosition(); + _buf.write( data ); + int after = _buf.getPosition(); + com.massivecraft.mcore3.lib.mongodb.util.MyAsserts.assertEquals( after - before , data.length ); + } + + protected void putUUID( String name , UUID val ){ + _put( BINARY , name ); + _buf.writeInt( 16 ); + _buf.write( B_UUID ); + _buf.writeLong( val.getMostSignificantBits()); + _buf.writeLong( val.getLeastSignificantBits()); + } + + protected void putSymbol( String name , Symbol s ){ + _putString(name, s.getSymbol(), SYMBOL); + } + + protected void putString(String name, String s) { + _putString(name, s, STRING); + } + + private void _putString( String name , String s, byte type ){ + _put( type , name ); + _putValueString( s ); + } + + protected void putObjectId( String name , ObjectId oid ){ + _put( OID , name ); + // according to spec, values should be stored big endian + _buf.writeIntBE( oid._time() ); + _buf.writeIntBE( oid._machine() ); + _buf.writeIntBE( oid._inc() ); + } + + private void putPattern( String name, Pattern p ) { + _put( REGEX , name ); + _put( p.pattern() ); + _put( regexFlags( p.flags() ) ); + } + + private void putMinKey( String name ) { + _put( MINKEY , name ); + } + + private void putMaxKey( String name ) { + _put( MAXKEY , name ); + } + + + // ---------------------------------------------- + + /** + * Encodes the type and key. + * + */ + protected void _put( byte type , String name ){ + _buf.write( type ); + _put( name ); + } + + protected void _putValueString( String s ){ + int lenPos = _buf.getPosition(); + _buf.writeInt( 0 ); // making space for size + int strLen = _put( s ); + _buf.writeInt( lenPos , strLen ); + } + + void _reset( Buffer b ){ + b.position(0); + b.limit( b.capacity() ); + } + + /** + * puts as utf-8 string + */ + protected int _put( String str ){ + + final int len = str.length(); + int total = 0; + + for ( int i=0; i> 6) ) ); + _buf.write( (byte)(0x80 + (c & 0x3f) ) ); + total += 2; + } + else if (c < 0x10000){ + _buf.write( (byte)(0xe0 + (c >> 12) ) ); + _buf.write( (byte)(0x80 + ((c >> 6) & 0x3f) ) ); + _buf.write( (byte)(0x80 + (c & 0x3f) ) ); + total += 3; + } + else { + _buf.write( (byte)(0xf0 + (c >> 18)) ); + _buf.write( (byte)(0x80 + ((c >> 12) & 0x3f)) ); + _buf.write( (byte)(0x80 + ((c >> 6) & 0x3f)) ); + _buf.write( (byte)(0x80 + (c & 0x3f)) ); + total += 4; + } + + i += Character.charCount(c); + } + + _buf.write( (byte)0 ); + total++; + return total; + } + + public void writeInt( int x ){ + _buf.writeInt( x ); + } + + public void writeLong( long x ){ + _buf.writeLong( x ); + } + + public void writeCString( String s ){ + _put( s ); + } + + protected OutputBuffer _buf; + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java new file mode 100644 index 00000000..f457922b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java @@ -0,0 +1,355 @@ +// BasicBSONObject.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +// BSON +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +// Java +import java.util.Map; +import java.util.Set; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.regex.Pattern; + +/** + * A simple implementation of DBObject. + * A DBObject can be created as follows, using this class: + *
+ * DBObject obj = new BasicBSONObject();
+ * obj.put( "foo", "bar" );
+ * 
+ */ +public class BasicBSONObject extends LinkedHashMap implements BSONObject { + + private static final long serialVersionUID = -4415279469780082174L; + + /** + * Creates an empty object. + */ + public BasicBSONObject(){ + } + + public BasicBSONObject(int size){ + super(size); + } + + /** + * Convenience CTOR + * @param key key under which to store + * @param value value to stor + */ + public BasicBSONObject(String key, Object value){ + put(key, value); + } + + /** + * Creates a DBObject from a map. + * @param m map to convert + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public BasicBSONObject(Map m) { + super(m); + } + + /** + * Converts a DBObject to a map. + * @return the DBObject + */ + @SuppressWarnings("rawtypes") + public Map toMap() { + return new LinkedHashMap(this); + } + + /** Deletes a field from this object. + * @param key the field name to remove + * @return the object removed + */ + public Object removeField( String key ){ + return remove( key ); + } + + /** Checks if this object contains a given field + * @param field field name + * @return if the field exists + */ + public boolean containsField( String field ){ + return super.containsKey(field); + } + + /** + * @deprecated + */ + @Deprecated + public boolean containsKey( String key ){ + return containsField(key); + } + + /** Gets a value from this object + * @param key field name + * @return the value + */ + public Object get( String key ){ + return super.get(key); + } + + /** Returns the value of a field as an int. + * @param key the field to look for + * @return the field value (or default) + */ + public int getInt( String key ){ + Object o = get(key); + if ( o == null ) + throw new NullPointerException( "no value for: " + key ); + + return BSON.toInt( o ); + } + + /** Returns the value of a field as an int. + * @param key the field to look for + * @param def the default to return + * @return the field value (or default) + */ + public int getInt( String key , int def ){ + Object foo = get( key ); + if ( foo == null ) + return def; + + return BSON.toInt( foo ); + } + + /** + * Returns the value of a field as a long. + * + * @param key the field to return + * @return the field value + */ + public long getLong( String key){ + Object foo = get( key ); + return ((Number)foo).longValue(); + } + + /** + * Returns the value of a field as an long. + * @param key the field to look for + * @param def the default to return + * @return the field value (or default) + */ + public long getLong( String key , long def ) { + Object foo = get( key ); + if ( foo == null ) + return def; + + return ((Number)foo).longValue(); + } + + /** + * Returns the value of a field as a double. + * + * @param key the field to return + * @return the field value + */ + public double getDouble( String key){ + Object foo = get( key ); + return ((Number)foo).doubleValue(); + } + + /** + * Returns the value of a field as an double. + * @param key the field to look for + * @param def the default to return + * @return the field value (or default) + */ + public double getDouble( String key , double def ) { + Object foo = get( key ); + if ( foo == null ) + return def; + + return ((Number)foo).doubleValue(); + } + + /** Returns the value of a field as a string + * @param key the field to look up + * @return the value of the field, converted to a string + */ + public String getString( String key ){ + Object foo = get( key ); + if ( foo == null ) + return null; + return foo.toString(); + } + + /** + * Returns the value of a field as a string + * @param key the field to look up + * @param def the default to return + * @return the value of the field, converted to a string + */ + public String getString( String key, final String def ) { + Object foo = get( key ); + if ( foo == null ) + return def; + + return foo.toString(); + } + + /** Returns the value of a field as a boolean. + * @param key the field to look up + * @return the value of the field, or false if field does not exist + */ + public boolean getBoolean( String key ){ + return getBoolean(key, false); + } + + /** Returns the value of a field as a boolean + * @param key the field to look up + * @param def the default value in case the field is not found + * @return the value of the field, converted to a string + */ + public boolean getBoolean( String key , boolean def ){ + Object foo = get( key ); + if ( foo == null ) + return def; + if ( foo instanceof Number ) + return ((Number)foo).intValue() > 0; + if ( foo instanceof Boolean ) + return ((Boolean)foo).booleanValue(); + throw new IllegalArgumentException( "can't coerce to bool:" + foo.getClass() ); + } + + /** + * Returns the object id or null if not set. + * @param field The field to return + * @return The field object value or null if not found (or if null :-^). + */ + public ObjectId getObjectId( final String field ) { + return (ObjectId) get( field ); + } + + /** + * Returns the object id or def if not set. + * @param field The field to return + * @param def the default value in case the field is not found + * @return The field object value or def if not set. + */ + public ObjectId getObjectId( final String field, final ObjectId def ) { + final Object foo = get( field ); + return (foo != null) ? (ObjectId)foo : def; + } + + /** + * Returns the date or null if not set. + * @param field The field to return + * @return The field object value or null if not found. + */ + public Date getDate( final String field ) { + return (Date) get( field ); + } + + /** + * Returns the date or def if not set. + * @param field The field to return + * @param def the default value in case the field is not found + * @return The field object value or def if not set. + */ + public Date getDate( final String field, final Date def ) { + final Object foo = get( field ); + return (foo != null) ? (Date)foo : def; + } + + /** Add a key/value pair to this object + * @param key the field name + * @param val the field value + * @return the val parameter + */ + public Object put( String key , Object val ){ + return super.put( key , val ); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void putAll( Map m ){ + for ( Map.Entry entry : (Set)m.entrySet() ){ + put( entry.getKey().toString() , entry.getValue() ); + } + } + + public void putAll( BSONObject o ){ + for ( String k : o.keySet() ){ + put( k , o.get( k ) ); + } + } + + /** Add a key/value pair to this object + * @param key the field name + * @param val the field value + * @return this + */ + public BasicBSONObject append( String key , Object val ){ + put( key , val ); + + return this; + } + + /** Returns a JSON serialization of this object + * @return JSON serialization + */ + public String toString(){ + return com.massivecraft.mcore3.lib.mongodb.util.JSON.serialize( this ); + } + + public boolean equals( Object o ){ + if ( ! ( o instanceof BSONObject ) ) + return false; + + BSONObject other = (BSONObject)o; + if ( ! keySet().equals( other.keySet() ) ) + return false; + + for ( String key : keySet() ){ + Object a = get( key ); + Object b = other.get( key ); + + if ( a == null ){ + if ( b != null ) + return false; + } + if ( b == null ){ + if ( a != null ) + return false; + } + else if ( a instanceof Number && b instanceof Number ){ + if ( ((Number)a).doubleValue() != + ((Number)b).doubleValue() ) + return false; + } + else if ( a instanceof Pattern && b instanceof Pattern ){ + Pattern p1 = (Pattern) a; + Pattern p2 = (Pattern) b; + if (!p1.pattern().equals(p2.pattern()) || p1.flags() != p2.flags()) + return false; + } + else { + if ( ! a.equals( b ) ) + return false; + } + } + return true; + } + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java new file mode 100644 index 00000000..9db633fc --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java @@ -0,0 +1,143 @@ +/** + * Copyright (C) 2011 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +public class EmptyBSONCallback implements BSONCallback { + + public void objectStart(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void objectStart( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void objectStart( boolean array ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public Object objectDone(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public BSONCallback createBSONCallback(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void arrayStart(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void arrayStart( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public Object arrayDone(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotNull( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotUndefined( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotMinKey( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotMaxKey( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotBoolean( String name , boolean v ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotDouble( String name , double v ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotInt( String name , int v ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotLong( String name , long v ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotDate( String name , long millis ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotString( String name , String v ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotSymbol( String name , String v ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotRegex( String name , String pattern , String flags ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotTimestamp( String name , int time , int inc ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotObjectId( String name , ObjectId id ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotDBRef( String name , String ns , ObjectId id ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @Deprecated + public void gotBinaryArray( String name , byte[] data ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotUUID( String name , long part1 , long part2 ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotCode( String name , String code ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotCodeWScope( String name , String code , Object scope ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void reset(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public Object get(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void gotBinary( String name , byte type , byte[] data ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + +} \ No newline at end of file diff --git a/src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java b/src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java new file mode 100644 index 00000000..d54b4d24 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2011 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import java.util.HashMap; + +import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer; + +/** + * @author brendan + * @author scotthernandez + */ +public class KeyCachingLazyBSONObject extends LazyBSONObject { + + public KeyCachingLazyBSONObject(byte[] data , LazyBSONCallback cbk) { super( data , cbk ); } + public KeyCachingLazyBSONObject(byte[] data , int offset , LazyBSONCallback cbk) { super( data , offset , cbk ); } + public KeyCachingLazyBSONObject( BSONByteBuffer buffer, LazyBSONCallback callback ){ super( buffer, callback ); } + public KeyCachingLazyBSONObject( BSONByteBuffer buffer, int offset, LazyBSONCallback callback ){ super( buffer, offset, callback ); } + + @Override + public Object get( String key ) { + ensureFieldList(); + return super.get( key ); + } + + @Override + public boolean containsField( String s ) { + ensureFieldList(); + if (! fieldIndex.containsKey( s ) ) + return false; + else + return super.containsField( s ); + } + + synchronized private void ensureFieldList() { + //only run once + if (fieldIndex == null) return; + try { + int offset = _doc_start_offset + FIRST_ELMT_OFFSET; + + while ( !isElementEmpty( offset ) ){ + int fieldSize = sizeCString( offset ); + int elementSize = getElementBSONSize( offset++ ); + String name = _input.getCString( offset ); + ElementRecord _t_record = new ElementRecord( name, offset ); + fieldIndex.put( name, _t_record ); + offset += ( fieldSize + elementSize ); + } + } catch (Exception e) { + fieldIndex = new HashMap(); + } + } + + + private HashMap fieldIndex = new HashMap(); + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java new file mode 100644 index 00000000..d3f80160 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import java.util.List; +import java.util.logging.Logger; + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; +import com.massivecraft.mcore3.lib.mongodb.LazyDBObject; + + +/** + * + */ +public class LazyBSONCallback extends EmptyBSONCallback { + + public void objectStart(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void objectStart( String name ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void objectStart( boolean array ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public Object objectDone(){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + public void reset(){ + _root = null; + } + + public Object get(){ + return _root; + } + + public void gotBinary( String name, byte type, byte[] data ){ + setRootObject( createObject( data, 0 ) ); + } + + public void setRootObject( Object root ){ + _root = root; + } + + public Object createObject( byte[] data, int offset ){ + return new LazyDBObject( data, offset, this ); + } + + @SuppressWarnings("rawtypes") + public List createArray( byte[] data, int offset ){ + return new LazyDBList( data, offset, this ); + } + + public Object createDBRef( String ns, ObjectId id ){ + return new BasicBSONObject( "$ns", ns ).append( "$id", id ); + } + + + /* public Object createObject(InputStream input, int offset) { + try { + return new LazyBSONObject(input, offset, this); + } + catch ( IOException e ) { + e.printStackTrace(); + return null; + } + }*/ + private Object _root; + @SuppressWarnings("unused") + private static final Logger log = Logger.getLogger( "org.bson.LazyBSONCallback" ); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java new file mode 100644 index 00000000..3c642ef0 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.io.Bits; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +/** + * implementation of BSONDecoder that creates LazyBSONObject instances + */ +public class LazyBSONDecoder implements BSONDecoder { + static final Logger LOG = Logger.getLogger( LazyBSONDecoder.class.getName() ); + + public BSONObject readObject(byte[] b) { + try { + return readObject( new ByteArrayInputStream( b ) ); + } + catch ( IOException ioe ){ + throw new BSONException( "should be impossible" , ioe ); + } + } + + public BSONObject readObject(InputStream in) throws IOException { + BSONCallback c = new LazyBSONCallback(); + decode( in , c ); + return (BSONObject)c.get(); + } + + public int decode(byte[] b, BSONCallback callback) { + try { + return decode( new ByteArrayInputStream( b ), callback ); + } + catch ( IOException ioe ) { + throw new BSONException( "should be impossible" , ioe ); + } + } + + public int decode(InputStream in, BSONCallback callback) throws IOException { + byte[] objSizeBuffer = new byte[BYTES_IN_INTEGER]; + Bits.readFully(in, objSizeBuffer, 0, BYTES_IN_INTEGER); + int objSize = Bits.readInt(objSizeBuffer); + byte[] data = new byte[objSize]; + System.arraycopy(objSizeBuffer, 0, data, 0, BYTES_IN_INTEGER); + + Bits.readFully(in, data, BYTES_IN_INTEGER, objSize - BYTES_IN_INTEGER); + + // note that we are handing off ownership of the data byte array to the callback + callback.gotBinary(null, (byte) 0, data); + return objSize; + } + + private static int BYTES_IN_INTEGER = 4; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java new file mode 100644 index 00000000..68df4fea --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java @@ -0,0 +1,177 @@ +package com.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer; + +import java.util.*; + +@SuppressWarnings( "rawtypes" ) +public class LazyBSONList extends LazyBSONObject implements List { + + public LazyBSONList(byte[] data , LazyBSONCallback callback) { super( data , callback ); } + public LazyBSONList(byte[] data , int offset , LazyBSONCallback callback) { super( data , offset , callback ); } + public LazyBSONList(BSONByteBuffer buffer , LazyBSONCallback callback) { super( buffer , callback ); } + public LazyBSONList(BSONByteBuffer buffer , int offset , LazyBSONCallback callback) { super( buffer , offset , callback ); } + + @Override + public boolean contains( Object arg0 ){ + return indexOf(arg0) > -1; + } + + @Override + public boolean containsAll( Collection arg0 ){ + for ( Object obj : arg0 ) { + if ( !contains( obj ) ) + return false; + } + return true; + } + + @Override + public Object get( int pos ){ + return get("" + pos); + } + + @Override + public Iterator iterator(){ + return new LazyBSONListIterator(); + } + + @Override + public int indexOf( Object arg0 ){ + int pos = 0; + Iterator it = iterator(); + while ( it.hasNext() ) { + Object curr = it.next(); + if ( arg0.equals( curr ) ) + return pos; + + pos++; + } + return -1; + } + + @Override + public int lastIndexOf( Object arg0 ){ + int pos = 0; + int lastFound = -1; + + Iterator it = iterator(); + while(it.hasNext()) { + Object curr = it.next(); + if(arg0.equals( curr )) + lastFound = pos; + + pos++; + } + + return lastFound; + } + + @Override + public int size(){ + //TODO check the last one and get the key/field name to see the ordinal position in case the array is stored with missing elements. + return getElements().size(); + } + + public class LazyBSONListIterator implements Iterator { + List elements; + int pos=0; + + public LazyBSONListIterator() { + elements = getElements(); + } + + @Override + public boolean hasNext(){ + return pos < elements.size(); + } + + @Override + public Object next(){ + return getElementValue(elements.get(pos++)); + } + + @Override + public void remove(){ + throw new UnsupportedOperationException( "Read Only" ); + } + + } + + @Override + public ListIterator listIterator( int arg0 ){ + throw new UnsupportedOperationException( "Not Supported" ); + } + + @Override + public ListIterator listIterator(){ + throw new UnsupportedOperationException( "Not Supported" ); + } + + + @Override + public boolean add( Object arg0 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public void add( int arg0 , Object arg1 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public boolean addAll( Collection arg0 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public boolean addAll( int arg0 , Collection arg1 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public void clear(){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public boolean remove( Object arg0 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public Object remove( int arg0 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public boolean removeAll( Collection arg0 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public boolean retainAll( Collection arg0 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public Object set( int arg0 , Object arg1 ){ + throw new UnsupportedOperationException( "Read Only" ); + } + + @Override + public List subList( int arg0 , int arg1 ){ + throw new UnsupportedOperationException( "Not Supported" ); + } + + @Override + public Object[] toArray(){ + throw new UnsupportedOperationException( "Not Supported" ); + } + + @Override + public Object[] toArray( Object[] arg0 ){ + throw new UnsupportedOperationException( "Not Supported" ); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java new file mode 100644 index 00000000..9ceed018 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java @@ -0,0 +1,691 @@ +/** + * Copyright (C) 2008-2011 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer; +import com.massivecraft.mcore3.lib.bson.types.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.*; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * @author antoine + * @author brendan + * @author scotthernandez + * @author Kilroy Wuz Here + */ +public class LazyBSONObject implements BSONObject { + + public LazyBSONObject( byte[] data, LazyBSONCallback callback ){ + this( BSONByteBuffer.wrap( data ), callback ); + } + + public LazyBSONObject( byte[] data, int offset, LazyBSONCallback callback ){ + this( BSONByteBuffer.wrap( data, offset, data.length - offset ), offset, callback ); + } + + public LazyBSONObject( BSONByteBuffer buffer, LazyBSONCallback callback ){ + this( buffer, 0, callback ); + } + + public LazyBSONObject( BSONByteBuffer buffer, int offset, LazyBSONCallback callback ){ + _callback = callback; + _input = buffer; + _doc_start_offset = offset; + } + + + class ElementRecord { + ElementRecord( final String name, final int offset ){ + this.name = name; + this.offset = offset; + this.type = getElementType( offset - 1 ); + this.fieldNameSize = sizeCString( offset ); + this.valueOffset = offset + fieldNameSize; + } + + final String name; + /** + * The offset the record begins at. + */ + final byte type; + final int fieldNameSize; + final int valueOffset; + final int offset; + } + + class LazyBSONKeyIterator implements Iterator { + + public boolean hasNext(){ + return !isElementEmpty( offset ); + } + + public String next(){ + int fieldSize = sizeCString( offset + 1); + int elementSize = getElementBSONSize( offset ); + String key = _input.getCString( offset + 1); + offset += fieldSize + elementSize + 1; + return key; + } + + public void remove(){ + throw new UnsupportedOperationException( "Read only" ); + } + + int offset = _doc_start_offset + FIRST_ELMT_OFFSET; + } + + public class LazyBSONKeySet extends ReadOnlySet { + + /** + * This method runs in time linear to the total size of all keys in the document. + * + * @return the number of keys in the document + */ + @Override + public int size(){ + int size = 0; + Iterator iter = iterator(); + while(iter.hasNext()) { + iter.next(); + ++size; + } + return size; + } + + @Override + public boolean isEmpty(){ + return LazyBSONObject.this.isEmpty(); + } + + @Override + public boolean contains( Object o ){ + for ( String key : this ){ + if ( key.equals( o ) ){ + return true; + } + } + return false; + } + + @Override + public Iterator iterator(){ + return new LazyBSONKeyIterator(); + } + + @Override + public String[] toArray(){ + String[] a = new String[size()]; + return toArray(a); + } + + @SuppressWarnings( "unchecked" ) + @Override + public T[] toArray(T[] a) { + int size = size(); + + T[] localArray = a.length >= size ? a : + (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); + + int i = 0; + for ( String key : this ){ + localArray[i++] = (T) key; + } + + if (localArray.length > i) { + localArray[i] = null; + } + return localArray; + } + + @Override + public boolean add( String e ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @Override + public boolean remove( Object o ){ + throw new UnsupportedOperationException( "Not supported yet." ); + } + + @Override + public boolean containsAll( Collection collection ){ + for ( Object item : collection ){ + if ( !contains( item ) ){ + return false; + } + } + return true; + } + } + + class LazyBSONEntryIterator implements Iterator> { + + public boolean hasNext(){ + return !isElementEmpty( offset ); + } + + public Map.Entry next(){ + int fieldSize = sizeCString(offset + 1); + int elementSize = getElementBSONSize(offset); + String key = _input.getCString(offset + 1); + final ElementRecord nextElementRecord = new ElementRecord(key, ++offset); + offset += fieldSize + elementSize; + return new Map.Entry() { + @Override + public String getKey() { + return nextElementRecord.name; + } + + @Override + public Object getValue() { + return getElementValue(nextElementRecord); + } + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException("Read only"); + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + return getKey().equals(e.getKey()) && getValue().equals(e.getValue()); + } + + @Override + public int hashCode() { + return getKey().hashCode() ^ getValue().hashCode(); + } + + @Override + public String toString() { + return getKey() + "=" + getValue(); + } + }; + } + + public void remove(){ + throw new UnsupportedOperationException( "Read only" ); + } + + int offset = _doc_start_offset + FIRST_ELMT_OFFSET; + } + + class LazyBSONEntrySet extends ReadOnlySet> { + @Override + public int size() { + return LazyBSONObject.this.keySet().size(); + } + + @Override + public boolean isEmpty() { + return LazyBSONObject.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + Iterator> iter = iterator(); + while (iter.hasNext()) { + if (iter.next().equals(o)) { + return true; + } + } + return false; + } + + @Override + public boolean containsAll(Collection c) { + for (Object cur : c) { + if (!contains(cur)) { + return false; + } + } + + return true; + } + + @Override + public Iterator> iterator() { + return new LazyBSONEntryIterator(); + } + + @SuppressWarnings("rawtypes") + @Override + public Object[] toArray() { + Map.Entry[] array = new Map.Entry[size()]; + return toArray(array); + } + + @SuppressWarnings( "unchecked" ) + @Override + public T[] toArray(T[] a) { + int size = size(); + + T[] localArray = a.length >= size ? a : + (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); + + Iterator> iter = iterator(); + int i = 0; + while(iter.hasNext()) { + localArray[i++] = (T) iter.next(); + } + + if (localArray.length > i) { + localArray[i] = null; + } + + return localArray; + } + } + + // Base class that throws UnsupportedOperationException for any method that writes to the Set + abstract class ReadOnlySet implements Set { + + @Override + public boolean add(E e) { + throw new UnsupportedOperationException("Read-only Set"); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException("Read-only Set"); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException("Read-only Set"); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException("Read-only Set"); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException("Read-only Set"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("Read-only Set"); + } + } + + public Object put( String key, Object v ){ + throw new UnsupportedOperationException( "Object is read only" ); + } + + public void putAll( BSONObject o ){ + throw new UnsupportedOperationException( "Object is read only" ); + } + + @SuppressWarnings("rawtypes") + public void putAll( Map m ){ + throw new UnsupportedOperationException( "Object is read only" ); + } + + public Object get( String key ){ + //get element up to the key + ElementRecord element = getElement(key); + + //no found if null/empty + if (element == null) { + return null; + } + + return getElementValue(element); + + } + + /** + * returns the ElementRecord for the given key, or null if not found + * @param key the field/key to find + * @return ElementRecord for key, or null + */ + ElementRecord getElement(String key){ + int offset = _doc_start_offset + FIRST_ELMT_OFFSET; + + while ( !isElementEmpty( offset ) ){ + int fieldSize = sizeCString( offset + 1 ); + int elementSize = getElementBSONSize( offset ); + String name = _input.getCString( ++offset); + + if (name.equals(key)) { + return new ElementRecord( name, offset ); + } + offset += ( fieldSize + elementSize); + } + + return null; + } + + + /** + * returns all the ElementRecords in this document + * @return list of ElementRecord + */ + List getElements(){ + int offset = _doc_start_offset + FIRST_ELMT_OFFSET; + ArrayList elements = new ArrayList(); + + while ( !isElementEmpty( offset ) ){ + int fieldSize = sizeCString( offset + 1 ); + int elementSize = getElementBSONSize( offset ); + String name = _input.getCString( ++offset ); + ElementRecord rec = new ElementRecord( name, offset ); + elements.add( rec ); + offset += ( fieldSize + elementSize ); + } + + return elements; + } + + @SuppressWarnings("rawtypes") + public Map toMap(){ + throw new UnsupportedOperationException( "Not Supported" ); + } + + public Object removeField( String key ){ + throw new UnsupportedOperationException( "Object is read only" ); + } + + @Deprecated + public boolean containsKey( String s ){ + return containsField( s ); + } + + public boolean containsField( String s ){ + return keySet().contains( s ); + } + + /** + * + * @return the set of all keys in the document + */ + public Set keySet(){ + return new LazyBSONKeySet(); + } + + /** + * This method will be more efficient than using a combination of keySet() and get(String key) + * @return the set of entries (key, value) in the document + */ + public Set> entrySet(){ + return new LazyBSONEntrySet(); + } + + protected boolean isElementEmpty( int offset ){ + return getElementType( offset ) == BSON.EOO; + } + + public boolean isEmpty(){ + return isElementEmpty( _doc_start_offset + FIRST_ELMT_OFFSET ); + } + + private int getBSONSize( final int offset ){ + return _input.getInt( offset ); + } + + public int getBSONSize(){ + return getBSONSize( _doc_start_offset ); + } + + public int pipe(OutputStream os) throws IOException { + os.write(_input.array(), _doc_start_offset, getBSONSize()); + return getBSONSize(); + } + + private String getElementFieldName( final int offset ){ + return _input.getCString( offset ); + } + + protected byte getElementType( final int offset ){ + return _input.get( offset ); + } + + protected int getElementBSONSize( int offset ){ + int x = 0; + byte type = getElementType( offset++ ); + int n = sizeCString( offset ); + int valueOffset = offset + n; + switch ( type ){ + case BSON.EOO: + case BSON.UNDEFINED: + case BSON.NULL: + case BSON.MAXKEY: + case BSON.MINKEY: + break; + case BSON.BOOLEAN: + x = 1; + break; + case BSON.NUMBER_INT: + x = 4; + break; + case BSON.TIMESTAMP: + case BSON.DATE: + case BSON.NUMBER_LONG: + case BSON.NUMBER: + x = 8; + break; + case BSON.OID: + x = 12; + break; + case BSON.SYMBOL: + case BSON.CODE: + case BSON.STRING: + x = _input.getInt( valueOffset ) + 4; + break; + case BSON.CODE_W_SCOPE: + x = _input.getInt( valueOffset ); + break; + case BSON.REF: + x = _input.getInt( valueOffset ) + 4 + 12; + break; + case BSON.OBJECT: + case BSON.ARRAY: + x = _input.getInt( valueOffset ); + break; + case BSON.BINARY: + x = _input.getInt( valueOffset ) + 4 + 1/*subtype*/; + break; + case BSON.REGEX: + // 2 cstrs + int part1 = sizeCString( valueOffset ); + int part2 = sizeCString( valueOffset + part1 ); + x = part1 + part2; + break; + default: + throw new BSONException( "Invalid type " + type + " for field " + getElementFieldName( offset ) ); + } + return x; + } + + + /** + * Returns the size of the BSON cstring at the given offset in the buffer + * @param offset the offset into the buffer + * @return the size of the BSON cstring, including the null terminator + */ + protected int sizeCString( int offset ){ + int end = offset; + while ( true ){ + byte b = _input.get( end ); + if ( b == 0 ) + break; + else + end++; + } + return end - offset + 1; + } + + protected Object getElementValue( ElementRecord record ){ + switch ( record.type ){ + case BSON.EOO: + case BSON.UNDEFINED: + case BSON.NULL: + return null; + case BSON.MAXKEY: + return new MaxKey(); + case BSON.MINKEY: + return new MinKey(); + case BSON.BOOLEAN: + return ( _input.get( record.valueOffset ) != 0 ); + case BSON.NUMBER_INT: + return _input.getInt( record.valueOffset ); + case BSON.TIMESTAMP: + int inc = _input.getInt( record.valueOffset ); + int time = _input.getInt( record.valueOffset + 4 ); + return new BSONTimestamp( time, inc ); + case BSON.DATE: + return new Date( _input.getLong( record.valueOffset ) ); + case BSON.NUMBER_LONG: + return _input.getLong( record.valueOffset ); + case BSON.NUMBER: + return Double.longBitsToDouble( _input.getLong( record.valueOffset ) ); + case BSON.OID: + return new ObjectId( _input.getIntBE( record.valueOffset ), + _input.getIntBE( record.valueOffset + 4 ), + _input.getIntBE( record.valueOffset + 8 ) ); + case BSON.SYMBOL: + return new Symbol( _input.getUTF8String( record.valueOffset ) ); + case BSON.CODE: + return new Code( _input.getUTF8String( record.valueOffset ) ); + case BSON.STRING: + return _input.getUTF8String( record.valueOffset ); + case BSON.CODE_W_SCOPE: + int strsize = _input.getInt( record.valueOffset + 4 ); + String code = _input.getUTF8String( record.valueOffset + 4 ); + BSONObject scope = + (BSONObject) _callback.createObject( _input.array(), record.valueOffset + 4 + 4 + strsize ); + return new CodeWScope( code, scope ); + case BSON.REF: + int csize = _input.getInt( record.valueOffset ); + String ns = _input.getCString( record.valueOffset + 4 ); + int oidOffset = record.valueOffset + csize + 4; + ObjectId oid = new ObjectId( _input.getIntBE( oidOffset ), + _input.getIntBE( oidOffset + 4 ), + _input.getIntBE( oidOffset + 8 ) ); + return _callback.createDBRef( ns, oid ); + case BSON.OBJECT: + return _callback.createObject( _input.array(), record.valueOffset ); + case BSON.ARRAY: + return _callback.createArray( _input.array(), record.valueOffset ); + case BSON.BINARY: + return readBinary( record.valueOffset ); + case BSON.REGEX: + int patternCStringSize = sizeCString( record.valueOffset ); + String pattern = _input.getCString( record.valueOffset ); + String flags = _input.getCString( record.valueOffset + patternCStringSize + 1 ); + return Pattern.compile( pattern, BSON.regexFlags( flags ) ); + default: + throw new BSONException( + "Invalid type " + record.type + " for field " + getElementFieldName( record.offset ) ); + } + } + + private Object readBinary( int valueOffset ){ + final int totalLen = _input.getInt( valueOffset ); + valueOffset += 4; + final byte bType = _input.get( valueOffset ); + valueOffset += 1; + + byte[] bin; + switch ( bType ){ + case BSON.B_GENERAL:{ + bin = new byte[totalLen]; + for ( int n = 0; n < totalLen; n++ ){ + bin[n] = _input.get( valueOffset + n ); + } + return bin; + } + case BSON.B_BINARY: + final int len = _input.getInt( valueOffset ); + if ( len + 4 != totalLen ) + throw new IllegalArgumentException( + "Bad Data Size; Binary Subtype 2. { actual len: " + len + " expected totalLen: " + totalLen + + "}" ); + valueOffset += 4; + bin = new byte[len]; + for ( int n = 0; n < len; n++ ){ + bin[n] = _input.get( valueOffset + n ); + } + return bin; + case BSON.B_UUID: + if ( totalLen != 16 ) + throw new IllegalArgumentException( + "Bad Data Size; Binary Subtype 3 (UUID). { total length: " + totalLen + " != 16" ); + + long part1 = _input.getLong( valueOffset ); + valueOffset += 8; + long part2 = _input.getLong( valueOffset ); + return new UUID( part1, part2 ); + } + + bin = new byte[totalLen]; + for ( int n = 0; n < totalLen; n++ ){ + bin[n] = _input.get( valueOffset + n ); + } + return bin; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LazyBSONObject that = (LazyBSONObject) o; + + return Arrays.equals(this._input.array(), that._input.array()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(_input.array()); + } + + /** + * Returns a JSON serialization of this object + * + * @return JSON serialization + */ + public String toString(){ + return com.massivecraft.mcore3.lib.mongodb.util.JSON.serialize( this ); + } + + /** + * In a "normal" (aka not embedded) doc, this will be the offset of the first element. + * + * In an embedded doc because we use ByteBuffers to avoid unecessary copying the offset must be manually set in + * _doc_start_offset + */ + final static int FIRST_ELMT_OFFSET = 4; + + protected final int _doc_start_offset; + + protected final BSONByteBuffer _input; // TODO - Guard this with synchronicity? + // callback is kept to create sub-objects on the fly + protected final LazyBSONCallback _callback; + @SuppressWarnings("unused") + private static final Logger log = Logger.getLogger( "org.bson.LazyBSONObject" ); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyDBList.java b/src/com/massivecraft/mcore3/lib/bson/LazyDBList.java new file mode 100644 index 00000000..655853b0 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/LazyDBList.java @@ -0,0 +1,42 @@ +/** + * + */ +package com.massivecraft.mcore3.lib.bson; + + +import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer; +import com.massivecraft.mcore3.lib.mongodb.DBObject; +import com.massivecraft.mcore3.lib.mongodb.util.JSON; + +/** + * @author scotthernandez + * + */ +public class LazyDBList extends LazyBSONList implements DBObject { + @SuppressWarnings("unused") + private static final long serialVersionUID = -4415279469780082174L; + + public LazyDBList(byte[] data, LazyBSONCallback callback) { super(data, callback); } + public LazyDBList(byte[] data, int offset, LazyBSONCallback callback) { super(data, offset, callback); } + public LazyDBList(BSONByteBuffer buffer, LazyBSONCallback callback) { super(buffer, callback); } + public LazyDBList(BSONByteBuffer buffer, int offset, LazyBSONCallback callback) { super(buffer, offset, callback); } + + /** + * Returns a JSON serialization of this object + * @return JSON serialization + */ + @Override + public String toString(){ + return JSON.serialize( this ); + } + + public boolean isPartialObject(){ + return _isPartialObject; + } + + public void markAsPartialObject(){ + _isPartialObject = true; + } + + private boolean _isPartialObject; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java new file mode 100644 index 00000000..b574ca4f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java @@ -0,0 +1,304 @@ +/** + * Copyright (C) 2012 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +import com.massivecraft.mcore3.lib.bson.io.Bits; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +import static com.massivecraft.mcore3.lib.bson.BSON.*; + +// Java +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.UnsupportedEncodingException; + +/** + * A new implementation of the bson decoder. + */ +public class NewBSONDecoder implements BSONDecoder { + + @Override + public BSONObject readObject(final byte [] pData) { + _length = pData.length; + final BasicBSONCallback c = new BasicBSONCallback(); + decode(pData, c); + return (BSONObject)c.get(); + } + + @Override + public BSONObject readObject(final InputStream pIn) throws IOException { + // Slurp in the data and convert to a byte array. + _length = Bits.readInt(pIn); + + if (_data == null || _data.length < _length) { + _data = new byte[_length]; + } + + (new DataInputStream(pIn)).readFully(_data, 4, (_length - 4)); + + return readObject(_data); + } + + @Override + public int decode(final byte [] pData, final BSONCallback pCallback) { + _data = pData; + _pos = 4; + _callback = pCallback; + _decode(); + return _length; + } + + @Override + public int decode(final InputStream pIn, final BSONCallback pCallback) throws IOException { + _length = Bits.readInt(pIn); + + if (_data == null || _data.length < _length) { + _data = new byte[_length]; + } + + (new DataInputStream(pIn)).readFully(_data, 4, (_length - 4)); + + return decode(_data, pCallback); + } + + private final void _decode() { + _callback.objectStart(); + while (decodeElement()); + _callback.objectDone(); + } + + private final String readCstr() { + int length = 0; + final int offset = _pos; + + while (_data[_pos++] != 0) length++; + + try { + return new String(_data, offset, length, DEFAULT_ENCODING); + } catch (final UnsupportedEncodingException uee) { + return new String(_data, offset, length); + } + } + + private final String readUtf8Str() { + final int length = Bits.readInt(_data, _pos); + _pos += 4; + + if (length <= 0 || length > MAX_STRING) throw new BSONException("String invalid - corruption"); + + try { + final String str = new String(_data, _pos, (length - 1), DEFAULT_ENCODING); + _pos += length; + return str; + + } catch (final UnsupportedEncodingException uee) { + throw new BSONException("What is in the db", uee); + } + } + + private final Object _readBasicObject() { + _pos += 4; + + final BSONCallback save = _callback; + final BSONCallback _basic = _callback.createBSONCallback(); + _callback = _basic; + _basic.reset(); + _basic.objectStart(false); + + while( decodeElement() ); + _callback = save; + return _basic.get(); + } + + private final void _binary(final String pName) { + + final int totalLen = Bits.readInt(_data, _pos); + _pos += 4; + + final byte bType = _data[_pos]; + _pos += 1; + + switch ( bType ){ + case B_GENERAL: { + final byte [] data = new byte[totalLen]; + + System.arraycopy(_data, _pos, data, 0, totalLen); + _pos += totalLen; + + _callback.gotBinary(pName, bType, data); + return; + } + + case B_BINARY: { + final int len = Bits.readInt(_data, _pos); + _pos += 4; + + if ( len + 4 != totalLen ) + throw new IllegalArgumentException( "bad data size subtype 2 len: " + len + " totalLen: " + totalLen ); + + final byte [] data = new byte[len]; + System.arraycopy(_data, _pos, data, 0, len); + _pos += len; + _callback.gotBinary(pName, bType, data); + return; + } + + case B_UUID: { + if ( totalLen != 16 ) + throw new IllegalArgumentException( "bad data size subtype 3 len: " + totalLen + " != 16"); + + final long part1 = Bits.readLong(_data, _pos); + _pos += 8; + + final long part2 = Bits.readLong(_data, _pos); + _pos += 8; + + _callback.gotUUID(pName, part1, part2); + return; + } + } + + final byte [] data = new byte[totalLen]; + System.arraycopy(_data, _pos, data, 0, totalLen); + _pos += totalLen; + + _callback.gotBinary(pName, bType, data); + } + + private final boolean decodeElement() { + + final byte type = _data[_pos]; + _pos += 1; + + if (type == EOO) return false; + + final String name = readCstr(); + + switch (type) { + case NULL: { _callback.gotNull(name); return true; } + + case UNDEFINED: { _callback.gotUndefined(name); return true; } + + case BOOLEAN: { _callback.gotBoolean(name, (_data[_pos] > 0)); _pos += 1; return true; } + + case NUMBER: { _callback.gotDouble(name, Double.longBitsToDouble(Bits.readLong(_data, _pos))); _pos += 8; return true; } + + case NUMBER_INT: { _callback.gotInt(name, Bits.readInt(_data, _pos)); _pos += 4; return true; } + + case NUMBER_LONG: { + _callback.gotLong(name, Bits.readLong(_data, _pos)); + _pos += 8; + return true; + } + + case SYMBOL: { _callback.gotSymbol(name, readUtf8Str()); return true; } + case STRING: { _callback.gotString(name, readUtf8Str()); return true; } + + case OID: { + // OID is stored as big endian + + final int p1 = Bits.readIntBE(_data, _pos); + _pos += 4; + + final int p2 = Bits.readIntBE(_data, _pos); + _pos += 4; + + final int p3 = Bits.readIntBE(_data, _pos); + _pos += 4; + + _callback.gotObjectId(name , new ObjectId(p1, p2, p3)); + return true; + } + + case REF: { + _pos += 4; + + final String ns = readCstr(); + + final int p1 = Bits.readInt(_data, _pos); + _pos += 4; + + final int p2 = Bits.readInt(_data, _pos); + _pos += 4; + + final int p3 = Bits.readInt(_data, _pos); + _pos += 4; + + _callback.gotDBRef(name , ns, new ObjectId(p1, p2, p3)); + + return true; + } + + case DATE: { _callback.gotDate(name , Bits.readLong(_data, _pos)); _pos += 8; return true; } + + + case REGEX: { + _callback.gotRegex(name, readCstr(), readCstr()); + return true; + } + + case BINARY: { _binary(name); return true; } + + case CODE: { _callback.gotCode(name, readUtf8Str()); return true; } + + case CODE_W_SCOPE: { + _pos += 4; + _callback.gotCodeWScope(name, readUtf8Str(), _readBasicObject()); + return true; + } + + case ARRAY: + _pos += 4; + _callback.arrayStart(name); + while (decodeElement()); + _callback.arrayDone(); + return true; + + case OBJECT: + _pos += 4; + _callback.objectStart(name); + while (decodeElement()); + _callback.objectDone(); + return true; + + case TIMESTAMP: + int i = Bits.readInt(_data, _pos); + _pos += 4; + + int time = Bits.readInt(_data, _pos); + _pos += 4; + + _callback.gotTimestamp(name, time, i); + return true; + + case MINKEY: _callback.gotMinKey(name); return true; + case MAXKEY: _callback.gotMaxKey(name); return true; + + default: throw new UnsupportedOperationException( "BSONDecoder doesn't understand type : " + type + " name: " + name ); + } + } + + private static final int MAX_STRING = ( 32 * 1024 * 1024 ); + private static final String DEFAULT_ENCODING = "UTF-8"; + + private byte [] _data; + private int _length; + private int _pos = 0; + private BSONCallback _callback; +} + diff --git a/src/com/massivecraft/mcore3/lib/bson/Transformer.java b/src/com/massivecraft/mcore3/lib/bson/Transformer.java new file mode 100644 index 00000000..1bee6960 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/Transformer.java @@ -0,0 +1,27 @@ +// Transformer.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson; + +public interface Transformer { + + /** + * @return the new object. return passed in object if no change + */ + public Object transform( Object o ); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java new file mode 100644 index 00000000..49a1e026 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java @@ -0,0 +1,143 @@ +/** + * Copyright (C) 2008-2011 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.io; + +import com.massivecraft.mcore3.lib.bson.*; + +import java.io.*; +import java.nio.*; + +/** + * Pseudo byte buffer, delegates as it is too hard to properly override / extend the ByteBuffer API + * + * @author brendan + */ +public class BSONByteBuffer { + + private BSONByteBuffer( ByteBuffer buf ){ + this.buf = buf; + buf.order( ByteOrder.LITTLE_ENDIAN ); + } + + public static BSONByteBuffer wrap( byte[] bytes, int offset, int length ){ + return new BSONByteBuffer( ByteBuffer.wrap( bytes, offset, length ) ); + } + + public static BSONByteBuffer wrap( byte[] bytes ){ + return new BSONByteBuffer( ByteBuffer.wrap( bytes ) ); + } + + public byte get( int i ){ + return buf.get(i); + } + + public ByteBuffer get( byte[] bytes, int offset, int length ){ + return buf.get(bytes, offset, length); + } + + public ByteBuffer get( byte[] bytes ){ + return buf.get(bytes); + } + + public byte[] array(){ + return buf.array(); + } + + public String toString(){ + return buf.toString(); + } + + public int hashCode(){ + return buf.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BSONByteBuffer that = (BSONByteBuffer) o; + + if (buf != null ? !buf.equals(that.buf) : that.buf != null) return false; + + return true; + } + + /** + * Gets a Little Endian Integer + * + * @param i Index to read from + * + * @return + */ + public int getInt( int i ){ + return getIntLE( i ); + } + + public int getIntLE( int i ){ + int x = 0; + x |= ( 0xFF & buf.get( i + 0 ) ) << 0; + x |= ( 0xFF & buf.get( i + 1 ) ) << 8; + x |= ( 0xFF & buf.get( i + 2 ) ) << 16; + x |= ( 0xFF & buf.get( i + 3 ) ) << 24; + return x; + } + + public int getIntBE( int i ){ + int x = 0; + x |= ( 0xFF & buf.get( i + 0 ) ) << 24; + x |= ( 0xFF & buf.get( i + 1 ) ) << 16; + x |= ( 0xFF & buf.get( i + 2 ) ) << 8; + x |= ( 0xFF & buf.get( i + 3 ) ) << 0; + return x; + } + + public long getLong( int i ){ + return buf.getLong( i ); + } + + public String getCString(int offset) { + int end = offset; + while (get(end) != 0) { + ++end; + } + int len = end - offset; + return new String(array(), offset, len); + } + + public String getUTF8String(int valueOffset) { + int size = getInt(valueOffset) - 1; + try { + return new String(array(), valueOffset + 4, size, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new BSONException( "Cannot decode string as UTF-8." ); + } + } + + public Buffer position( int i ){ + return buf.position(i); + } + + public Buffer reset(){ + return buf.reset(); + } + + public int size(){ + return getInt( 0 ); + } + + protected ByteBuffer buf; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java new file mode 100644 index 00000000..de215ea9 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java @@ -0,0 +1,119 @@ +// BasicOutputBuffer.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.io; + +import java.io.*; + +public class BasicOutputBuffer extends OutputBuffer { + + @Override + public void write(byte[] b){ + write( b , 0 , b.length ); + } + + @Override + public void write(byte[] b, int off, int len){ + _ensure( len ); + System.arraycopy( b , off , _buffer , _cur , len ); + _cur += len; + _size = Math.max( _cur , _size ); + } + @Override + public void write(int b){ + _ensure(1); + _buffer[_cur++] = (byte)(0xFF&b); + _size = Math.max( _cur , _size ); + } + + @Override + public int getPosition(){ + return _cur; + } + @Override + public void setPosition( int position ){ + _cur = position; + } + + @Override + public void seekEnd(){ + _cur = _size; + } + @Override + public void seekStart(){ + _cur = 0; + } + + /** + * @return size of data so far + */ + @Override + public int size(){ + return _size; + } + + /** + * @return bytes written + */ + @Override + public int pipe( OutputStream out ) + throws IOException { + out.write( _buffer , 0 , _size ); + return _size; + } + + /** + * @return bytes written + */ + public int pipe( DataOutput out ) + throws IOException { + out.write( _buffer , 0 , _size ); + return _size; + } + + + void _ensure( int more ){ + final int need = _cur + more; + if ( need < _buffer.length ) + return; + + int newSize = _buffer.length*2; + if ( newSize <= need ) + newSize = need + 128; + + byte[] n = new byte[newSize]; + System.arraycopy( _buffer , 0 , n , 0 , _size ); + _buffer = n; + } + + @Override + public String asString(){ + return new String( _buffer , 0 , _size ); + } + + @Override + public String asString( String encoding ) + throws UnsupportedEncodingException { + return new String( _buffer , 0 , _size , encoding ); + } + + + private int _cur; + private int _size; + private byte[] _buffer = new byte[512]; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/Bits.java b/src/com/massivecraft/mcore3/lib/bson/io/Bits.java new file mode 100644 index 00000000..afb63c12 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/Bits.java @@ -0,0 +1,115 @@ +// Bits.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.io; + +import java.io.*; + +public class Bits { + + public static void readFully( InputStream in, byte[] b ) + throws IOException { + readFully( in , b , b.length ); + } + + public static void readFully( InputStream in, byte[] b, int length ) + throws IOException { + readFully(in, b, 0, length); + } + + public static void readFully( InputStream in, byte[] b, int startOffset, int length ) + throws IOException { + + if (b.length - startOffset > length) { + throw new IllegalArgumentException("Buffer is too small"); + } + + int offset = startOffset; + int toRead = length; + while ( toRead > 0 ){ + int bytesRead = in.read( b, offset , toRead ); + if ( bytesRead < 0 ) + throw new EOFException(); + toRead -= bytesRead; + offset += bytesRead; + } + } + + public static int readInt( InputStream in ) + throws IOException { + return readInt( in , new byte[4] ); + } + + public static int readInt( InputStream in , byte[] data ) + throws IOException { + readFully(in, data, 4); + return readInt(data); + } + + public static int readInt( byte[] data ) { + return readInt( data , 0 ); + } + + public static int readInt( byte[] data , int offset ) { + int x = 0; + x |= ( 0xFF & data[offset+0] ) << 0; + x |= ( 0xFF & data[offset+1] ) << 8; + x |= ( 0xFF & data[offset+2] ) << 16; + x |= ( 0xFF & data[offset+3] ) << 24; + return x; + } + + public static int readIntBE( byte[] data , int offset ) { + int x = 0; + x |= ( 0xFF & data[offset+0] ) << 24; + x |= ( 0xFF & data[offset+1] ) << 16; + x |= ( 0xFF & data[offset+2] ) << 8; + x |= ( 0xFF & data[offset+3] ) << 0; + return x; + } + + public static long readLong( InputStream in ) + throws IOException { + return readLong( in , new byte[8] ); + } + + + public static long readLong( InputStream in , byte[] data ) + throws IOException { + readFully(in, data, 8); + return readLong(data); + } + + public static long readLong( byte[] data ) { + return readLong( data , 0 ); + } + + public static long readLong( byte[] data , int offset ) { + long x = 0; + x |= ( 0xFFL & data[offset+0] ) << 0; + x |= ( 0xFFL & data[offset+1] ) << 8; + x |= ( 0xFFL & data[offset+2] ) << 16; + x |= ( 0xFFL & data[offset+3] ) << 24; + x |= ( 0xFFL & data[offset+4] ) << 32; + x |= ( 0xFFL & data[offset+5] ) << 40; + x |= ( 0xFFL & data[offset+6] ) << 48; + x |= ( 0xFFL & data[offset+7] ) << 56; + return x; + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java new file mode 100644 index 00000000..12b6c07b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java @@ -0,0 +1,159 @@ +// OutputBuffer.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.io; + +import java.io.*; +import java.security.*; + +public abstract class OutputBuffer extends OutputStream { + + public abstract void write(byte[] b); + public abstract void write(byte[] b, int off, int len); + public abstract void write(int b); + + public abstract int getPosition(); + public abstract void setPosition( int position ); + + public abstract void seekEnd(); + public abstract void seekStart(); + + /** + * @return size of data so far + */ + public abstract int size(); + + /** + * @return bytes written + */ + public abstract int pipe( OutputStream out ) + throws IOException; + + /** + * mostly for testing + */ + public byte [] toByteArray(){ + try { + final ByteArrayOutputStream bout = new ByteArrayOutputStream( size() ); + pipe( bout ); + return bout.toByteArray(); + } + catch ( IOException ioe ){ + throw new RuntimeException( "should be impossible" , ioe ); + } + } + + public String asString(){ + return new String( toByteArray() ); + } + + public String asString( String encoding ) + throws UnsupportedEncodingException { + return new String( toByteArray() , encoding ); + } + + + public String hex(){ + final StringBuilder buf = new StringBuilder(); + try { + pipe( new OutputStream(){ + public void write( int b ){ + String s = Integer.toHexString(0xff & b); + + if (s.length() < 2) + buf.append("0"); + buf.append(s); + } + } + ); + } + catch ( IOException ioe ){ + throw new RuntimeException( "impossible" ); + } + return buf.toString(); + } + + public String md5(){ + final MessageDigest md5 ; + try { + md5 = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Error - this implementation of Java doesn't support MD5."); + } + md5.reset(); + + try { + pipe( new OutputStream(){ + public void write( byte[] b , int off , int len ){ + md5.update( b , off , len ); + } + + public void write( int b ){ + md5.update( (byte)(b&0xFF) ); + } + } + ); + } + catch ( IOException ioe ){ + throw new RuntimeException( "impossible" ); + } + + return com.massivecraft.mcore3.lib.mongodb.util.Util.toHex( md5.digest() ); + } + + public void writeInt( int x ){ + write( x >> 0 ); + write( x >> 8 ); + write( x >> 16 ); + write( x >> 24 ); + } + + public void writeIntBE( int x ){ + write( x >> 24 ); + write( x >> 16 ); + write( x >> 8 ); + write( x ); + } + + public void writeInt( int pos , int x ){ + final int save = getPosition(); + setPosition( pos ); + writeInt( x ); + setPosition( save ); + } + + public void writeLong( long x ){ + write( (byte)(0xFFL & ( x >> 0 ) ) ); + write( (byte)(0xFFL & ( x >> 8 ) ) ); + write( (byte)(0xFFL & ( x >> 16 ) ) ); + write( (byte)(0xFFL & ( x >> 24 ) ) ); + write( (byte)(0xFFL & ( x >> 32 ) ) ); + write( (byte)(0xFFL & ( x >> 40 ) ) ); + write( (byte)(0xFFL & ( x >> 48 ) ) ); + write( (byte)(0xFFL & ( x >> 56 ) ) ); + } + + public void writeDouble( double x ){ + writeLong( Double.doubleToRawLongBits( x ) ); + } + + public String toString(){ + return getClass().getName() + " size: " + size() + " pos: " + getPosition() ; + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java new file mode 100644 index 00000000..4062744c --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java @@ -0,0 +1,239 @@ +// PoolOutputBuffer.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.io; + + +import java.io.*; +import java.util.*; + +public class PoolOutputBuffer extends OutputBuffer { + + public static final int BUF_SIZE = 1024 * 16; + + public PoolOutputBuffer(){ + reset(); + } + + public void reset(){ + _cur.reset(); + _end.reset(); + + for ( int i=0; i<_fromPool.size(); i++ ) + _extra.done( _fromPool.get(i) ); + _fromPool.clear(); + } + + public int getPosition(){ + return _cur.pos(); + } + + public void setPosition( int position ){ + _cur.reset( position ); + } + + public void seekEnd(){ + _cur.reset( _end ); + } + + public void seekStart(){ + _cur.reset(); + } + + + public int size(){ + return _end.pos(); + } + + public void write(byte[] b){ + write( b , 0 , b.length ); + } + + public void write(byte[] b, int off, int len){ + while ( len > 0 ){ + byte[] bs = _cur(); + int space = Math.min( bs.length - _cur.y , len ); + System.arraycopy( b , off , bs , _cur.y , space ); + _cur.inc( space ); + len -= space; + off += space; + _afterWrite(); + } + } + + public void write(int b){ + byte[] bs = _cur(); + bs[_cur.getAndInc()] = (byte)(b&0xFF); + _afterWrite(); + } + + void _afterWrite(){ + + if ( _cur.pos() < _end.pos() ){ + // we're in the middle of the total space + // just need to make sure we're not at the end of a buffer + if ( _cur.y == BUF_SIZE ) + _cur.nextBuffer(); + return; + } + + _end.reset( _cur ); + + if ( _end.y < BUF_SIZE ) + return; + + _fromPool.add( _extra.get() ); + _end.nextBuffer(); + _cur.reset( _end ); + } + + byte[] _cur(){ + return _get( _cur.x ); + } + + byte[] _get( int z ){ + if ( z < 0 ) + return _mine; + return _fromPool.get(z); + } + + public int pipe( final OutputStream out ) + throws IOException { + + if ( out == null ) + throw new NullPointerException( "out is null" ); + + int total = 0; + + for ( int i=-1; i<_fromPool.size(); i++ ){ + final byte[] b = _get( i ); + final int amt = _end.len( i ); + out.write( b , 0 , amt ); + total += amt; + } + + return total; + } + + static class Position { + Position(){ + reset(); + } + + void reset(){ + x = -1; + y = 0; + } + + void reset( Position other ){ + x = other.x; + y = other.y; + } + + void reset( int pos ){ + x = ( pos / BUF_SIZE ) - 1; + y = pos % BUF_SIZE; + } + + int pos(){ + return ( ( x + 1 ) * BUF_SIZE ) + y; + } + + int getAndInc(){ + return y++; + } + + void inc( int amt ){ + y += amt; + if ( y > BUF_SIZE ) + throw new IllegalArgumentException( "something is wrong" ); + } + + void nextBuffer(){ + if ( y != BUF_SIZE ) + throw new IllegalArgumentException( "broken" ); + x++; + y = 0; + } + + int len( int which ){ + if ( which < x ) + return BUF_SIZE; + return y; + } + + public String toString(){ + return x + "," + y; + } + + int x; // which buffer -1 == _mine + int y; // position in buffer + } + + public String asAscii(){ + if ( _fromPool.size() > 0 ) + return super.asString(); + + final int m = size(); + final char c[] = m < _chars.length ? _chars : new char[m]; + + for ( int i=0; i 0 ) + return super.asString( encoding ); + + if ( encoding.equals( DEFAULT_ENCODING_1 ) || encoding.equals( DEFAULT_ENCODING_2) ){ + try { + return _encoding.decode( _mine , 0 , size() ); + } + catch ( IOException ioe ){ + // we failed, fall back + } + } + return new String( _mine , 0 , size() , encoding ); + } + + + final byte[] _mine = new byte[BUF_SIZE]; + final char[] _chars = new char[BUF_SIZE]; + final List _fromPool = new ArrayList(); + final UTF8Encoding _encoding = new UTF8Encoding(); + + private static final String DEFAULT_ENCODING_1 = "UTF-8"; + private static final String DEFAULT_ENCODING_2 = "UTF8"; + + private final Position _cur = new Position(); + private final Position _end = new Position(); + + private static com.massivecraft.mcore3.lib.bson.util.SimplePool _extra = + new com.massivecraft.mcore3.lib.bson.util.SimplePool( ( 1024 * 1024 * 10 ) / BUF_SIZE ){ + + protected byte[] createNew(){ + return new byte[BUF_SIZE]; + } + + }; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java b/src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java new file mode 100644 index 00000000..bcee4099 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java @@ -0,0 +1,202 @@ +// UTF8Encoding.java + + +/** + * from postgresql jdbc driver: + * postgresql-jdbc-9.0-801.src + + +Copyright (c) 1997-2008, PostgreSQL Global Development Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the PostgreSQL Global Development Group nor the names + of its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ + +/*------------------------------------------------------------------------- +* +* Copyright (c) 2003-2008, PostgreSQL Global Development Group +* +* IDENTIFICATION +* +* +*------------------------------------------------------------------------- +*/ + +//package org.postgresql.core; +package com.massivecraft.mcore3.lib.bson.io; + +import java.io.IOException; +import java.text.MessageFormat; + +class UTF8Encoding { + + private static final int MIN_2_BYTES = 0x80; + private static final int MIN_3_BYTES = 0x800; + private static final int MIN_4_BYTES = 0x10000; + private static final int MAX_CODE_POINT = 0x10ffff; + + private char[] decoderArray = new char[1024]; + + // helper for decode + private final static void checkByte(int ch, int pos, int len) throws IOException { + if ((ch & 0xc0) != 0x80) + throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: byte {0} of {1} byte sequence is not 10xxxxxx: {2}", + new Object[] { new Integer(pos), new Integer(len), new Integer(ch) })); + } + + private final static void checkMinimal(int ch, int minValue) throws IOException { + if (ch >= minValue) + return; + + int actualLen; + switch (minValue) { + case MIN_2_BYTES: + actualLen = 2; + break; + case MIN_3_BYTES: + actualLen = 3; + break; + case MIN_4_BYTES: + actualLen = 4; + break; + default: + throw new IllegalArgumentException("unexpected minValue passed to checkMinimal: " + minValue); + } + + int expectedLen; + if (ch < MIN_2_BYTES) + expectedLen = 1; + else if (ch < MIN_3_BYTES) + expectedLen = 2; + else if (ch < MIN_4_BYTES) + expectedLen = 3; + else + throw new IllegalArgumentException("unexpected ch passed to checkMinimal: " + ch); + + throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: {0} bytes used to encode a {1} byte value: {2}", + new Object[] { new Integer(actualLen), new Integer(expectedLen), new Integer(ch) })); + } + + /** + * Custom byte[] -> String conversion routine for UTF-8 only. + * This is about twice as fast as using the String(byte[],int,int,String) + * ctor, at least under JDK 1.4.2. The extra checks for illegal representations + * add about 10-15% overhead, but they seem worth it given the number of SQL_ASCII + * databases out there. + * + * @param data the array containing UTF8-encoded data + * @param offset the offset of the first byte in data to decode from + * @param length the number of bytes to decode + * @return a decoded string + * @throws IOException if something goes wrong + */ + public synchronized String decode(byte[] data, int offset, int length) throws IOException { + char[] cdata = decoderArray; + if (cdata.length < length) + cdata = decoderArray = new char[length]; + + int in = offset; + int out = 0; + int end = length + offset; + + try + { + while (in < end) + { + int ch = data[in++] & 0xff; + + // Convert UTF-8 to 21-bit codepoint. + if (ch < 0x80) { + // 0xxxxxxx -- length 1. + } else if (ch < 0xc0) { + // 10xxxxxx -- illegal! + throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: initial byte is {0}: {1}", + new Object[] { "10xxxxxx", new Integer(ch) })); + } else if (ch < 0xe0) { + // 110xxxxx 10xxxxxx + ch = ((ch & 0x1f) << 6); + checkByte(data[in], 2, 2); + ch = ch | (data[in++] & 0x3f); + checkMinimal(ch, MIN_2_BYTES); + } else if (ch < 0xf0) { + // 1110xxxx 10xxxxxx 10xxxxxx + ch = ((ch & 0x0f) << 12); + checkByte(data[in], 2, 3); + ch = ch | ((data[in++] & 0x3f) << 6); + checkByte(data[in], 3, 3); + ch = ch | (data[in++] & 0x3f); + checkMinimal(ch, MIN_3_BYTES); + } else if (ch < 0xf8) { + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + ch = ((ch & 0x07) << 18); + checkByte(data[in], 2, 4); + ch = ch | ((data[in++] & 0x3f) << 12); + checkByte(data[in], 3, 4); + ch = ch | ((data[in++] & 0x3f) << 6); + checkByte(data[in], 4, 4); + ch = ch | (data[in++] & 0x3f); + checkMinimal(ch, MIN_4_BYTES); + } else { + throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: initial byte is {0}: {1}", + new Object[] { "11111xxx", new Integer(ch) })); + } + + if (ch > MAX_CODE_POINT) + throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: final value is out of range: {0}", + new Integer(ch))); + + // Convert 21-bit codepoint to Java chars: + // 0..ffff are represented directly as a single char + // 10000..10ffff are represented as a "surrogate pair" of two chars + // See: http://java.sun.com/developer/technicalArticles/Intl/Supplementary/ + + if (ch > 0xffff) { + // Use a surrogate pair to represent it. + ch -= 0x10000; // ch is now 0..fffff (20 bits) + cdata[out++] = (char) (0xd800 + (ch >> 10)); // top 10 bits + cdata[out++] = (char) (0xdc00 + (ch & 0x3ff)); // bottom 10 bits + } else if (ch >= 0xd800 && ch < 0xe000) { + // Not allowed to encode the surrogate range directly. + throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: final value is a surrogate value: {0}", + new Integer(ch))); + } else { + // Normal case. + cdata[out++] = (char) ch; + } + } + } + catch (ArrayIndexOutOfBoundsException a) + { + throw new IOException("Illegal UTF-8 sequence: multibyte sequence was truncated"); + } + + // Check if we ran past the end without seeing an exception. + if (in > end) + throw new IOException("Illegal UTF-8 sequence: multibyte sequence was truncated"); + + return new String(cdata, 0, out); + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/io/package.html b/src/com/massivecraft/mcore3/lib/bson/io/package.html new file mode 100644 index 00000000..8bf6c44f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/io/package.html @@ -0,0 +1,3 @@ + +

Contains classes implementing I/O operations used by BSON objects.

+ diff --git a/src/com/massivecraft/mcore3/lib/bson/package.html b/src/com/massivecraft/mcore3/lib/bson/package.html new file mode 100644 index 00000000..d10d9f8a --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/package.html @@ -0,0 +1,3 @@ + +

Contains the base BSON classes and Encoder/Decoder.

+ diff --git a/src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java b/src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java new file mode 100644 index 00000000..a74d9979 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java @@ -0,0 +1,76 @@ +// BSONTimestamp.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import java.io.Serializable; +import java.util.Date; + +/** + * this is used for internal increment values. + * for storing normal dates in MongoDB, you should use java.util.Date + * time is seconds since epoch + * inc is an ordinal + */ +public class BSONTimestamp implements Serializable { + + private static final long serialVersionUID = -3268482672267936464L; + + static final boolean D = Boolean.getBoolean( "DEBUG.DBTIMESTAMP" ); + + public BSONTimestamp(){ + _inc = 0; + _time = null; + } + + public BSONTimestamp(int time, int inc ){ + _time = new Date( time * 1000L ); + _inc = inc; + } + + /** + * @return get time in seconds since epoch + */ + public int getTime(){ + if ( _time == null ) + return 0; + return (int)(_time.getTime() / 1000); + } + + public int getInc(){ + return _inc; + } + + public String toString(){ + return "TS time:" + _time + " inc:" + _inc; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj instanceof BSONTimestamp) { + BSONTimestamp t2 = (BSONTimestamp) obj; + return getTime() == t2.getTime() && getInc() == t2.getInc(); + } + return false; + } + + final int _inc; + final Date _time; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java b/src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java new file mode 100644 index 00000000..d2336dee --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java @@ -0,0 +1,165 @@ +// BasicBSONList.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import com.massivecraft.mcore3.lib.bson.*; +import com.massivecraft.mcore3.lib.bson.util.StringRangeSet; + +import java.util.*; + +/** + * Utility class to allow array DBObjects to be created. + *

+ * Note: MongoDB will also create arrays from java.util.Lists. + *

+ *

+ *

+ * DBObject obj = new BasicBSONList();
+ * obj.put( "0", value1 );
+ * obj.put( "4", value2 );
+ * obj.put( 2, value3 );
+ * 
+ * This simulates the array [ value1, null, value3, null, value2 ] by creating the + * DBObject { "0" : value1, "1" : null, "2" : value3, "3" : null, "4" : value2 }. + *

+ *

+ * BasicBSONList only supports numeric keys. Passing strings that cannot be converted to ints will cause an + * IllegalArgumentException. + *

+ * BasicBSONList list = new BasicBSONList();
+ * list.put("1", "bar"); // ok
+ * list.put("1E1", "bar"); // throws exception
+ * 
+ *

+ */ +public class BasicBSONList extends ArrayList implements BSONObject { + + private static final long serialVersionUID = -4415279469780082174L; + + public BasicBSONList() { } + + /** + * Puts a value at an index. + * For interface compatibility. Must be passed a String that is parsable to an int. + * @param key the index at which to insert the value + * @param v the value to insert + * @return the value + * @throws IllegalArgumentException if key cannot be parsed into an int + */ + public Object put( String key , Object v ){ + return put(_getInt( key ), v); + } + + /** + * Puts a value at an index. + * This will fill any unset indexes less than index with null. + * @param key the index at which to insert the value + * @param v the value to insert + * @return the value + */ + public Object put( int key, Object v ) { + while ( key >= size() ) + add( null ); + set( key , v ); + return v; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void putAll( Map m ){ + for ( Map.Entry entry : (Set)m.entrySet() ){ + put( entry.getKey().toString() , entry.getValue() ); + } + } + + public void putAll( BSONObject o ){ + for ( String k : o.keySet() ){ + put( k , o.get( k ) ); + } + } + + /** + * Gets a value at an index. + * For interface compatibility. Must be passed a String that is parsable to an int. + * @param key the index + * @return the value, if found, or null + * @throws IllegalArgumentException if key cannot be parsed into an int + */ + public Object get( String key ){ + int i = _getInt( key ); + if ( i < 0 ) + return null; + if ( i >= size() ) + return null; + return get( i ); + } + + public Object removeField( String key ){ + int i = _getInt( key ); + if ( i < 0 ) + return null; + if ( i >= size() ) + return null; + return remove( i ); + } + + /** + * @deprecated + */ + @Deprecated + public boolean containsKey( String key ){ + return containsField(key); + } + + public boolean containsField( String key ){ + int i = _getInt( key , false ); + if ( i < 0 ) + return false; + return i >= 0 && i < size(); + } + + public Set keySet(){ + return new StringRangeSet(size()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Map toMap() { + Map m = new HashMap(); + Iterator i = this.keySet().iterator(); + while (i.hasNext()) { + Object s = i.next(); + m.put(s, this.get(String.valueOf(s))); + } + return m; + } + + int _getInt( String s ){ + return _getInt( s , true ); + } + + int _getInt( String s , boolean err ){ + try { + return Integer.parseInt( s ); + } + catch ( Exception e ){ + if ( err ) + throw new IllegalArgumentException( "BasicBSONList can only work with numeric keys, not: [" + s + "]" ); + return -1; + } + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/types/Binary.java b/src/com/massivecraft/mcore3/lib/bson/types/Binary.java new file mode 100644 index 00000000..7f0fdf89 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/Binary.java @@ -0,0 +1,67 @@ +// Binary.java + +/** + * See the NOTICE.txt file distributed with this work for + * information regarding copyright ownership. + * + * The authors license 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 com.massivecraft.mcore3.lib.bson.types; + +import com.massivecraft.mcore3.lib.bson.BSON; + +import java.io.Serializable; + +/** + generic binary holder + */ +public class Binary implements Serializable { + + private static final long serialVersionUID = 7902997490338209467L; + + /** + * Creates a Binary object with the default binary type of 0 + * @param data raw data + */ + public Binary( byte[] data ){ + this(BSON.B_GENERAL, data); + } + + /** + * Creates a Binary object + * @param type type of the field as encoded in BSON + * @param data raw data + */ + public Binary( byte type , byte[] data ){ + _type = type; + _data = data; + } + + public byte getType(){ + return _type; + } + + public byte[] getData(){ + return _data; + } + + public int length(){ + return _data.length; + } + + final byte _type; + final byte[] _data; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/types/Code.java b/src/com/massivecraft/mcore3/lib/bson/types/Code.java new file mode 100644 index 00000000..1192f9f7 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/Code.java @@ -0,0 +1,58 @@ +// Code.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import java.io.Serializable; + +/** + * for using the Code type + */ +public class Code implements Serializable { + + private static final long serialVersionUID = 475535263314046697L; + + public Code( String code ){ + _code = code; + } + + public String getCode(){ + return _code; + } + + public boolean equals( Object o ){ + if ( ! ( o instanceof Code ) ) + return false; + + Code c = (Code)o; + return _code.equals( c._code ); + } + + public int hashCode(){ + return _code.hashCode(); + } + + @Override + public String toString() { + return getCode(); + } + + final String _code; + +} + diff --git a/src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java b/src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java new file mode 100644 index 00000000..4b11cfaf --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java @@ -0,0 +1,53 @@ +// CodeWScope.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import com.massivecraft.mcore3.lib.bson.*; + +/** + * for using the CodeWScope type + */ +public class CodeWScope extends Code { + + private static final long serialVersionUID = -6284832275113680002L; + + public CodeWScope( String code , BSONObject scope ){ + super( code ); + _scope = scope; + } + + public BSONObject getScope(){ + return _scope; + } + + public boolean equals( Object o ){ + if ( ! ( o instanceof CodeWScope ) ) + return false; + + CodeWScope c = (CodeWScope)o; + return _code.equals( c._code ) && _scope.equals( c._scope ); + } + + public int hashCode(){ + return _code.hashCode() ^ _scope.hashCode(); + } + + final BSONObject _scope; +} + diff --git a/src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java b/src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java new file mode 100644 index 00000000..4db5f4dd --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java @@ -0,0 +1,47 @@ + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import java.io.Serializable; + +/** + * Represent the maximum key value regardless of the key's type + */ +public class MaxKey implements Serializable { + + private static final long serialVersionUID = 5123414776151687185L; + + public MaxKey() { + } + + @Override + public boolean equals(Object o) { + return o instanceof MaxKey; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "MaxKey"; + } + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/types/MinKey.java b/src/com/massivecraft/mcore3/lib/bson/types/MinKey.java new file mode 100644 index 00000000..06cfb955 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/MinKey.java @@ -0,0 +1,47 @@ + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import java.io.Serializable; + +/** + * Represent the minimum key value regardless of the key's type + */ +public class MinKey implements Serializable { + + private static final long serialVersionUID = 4075901136671855684L; + + public MinKey() { + } + + @Override + public boolean equals(Object o) { + return o instanceof MinKey; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return "MinKey"; + } + +} diff --git a/src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java b/src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java new file mode 100644 index 00000000..2ce9dc27 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java @@ -0,0 +1,401 @@ +// ObjectId.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import java.net.*; +import java.nio.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; + +/** + * A globally unique identifier for objects. + *

Consists of 12 bytes, divided as follows: + *

+ * 
+ * 
+ *     
+ * 
+ *     
+ * 
01234567891011
timemachinepidinc
+ *
+ * + * @dochub objectids + */ +public class ObjectId implements Comparable , java.io.Serializable { + + private static final long serialVersionUID = -4415279469780082174L; + + static final Logger LOGGER = Logger.getLogger( "org.bson.ObjectId" ); + + /** Gets a new object id. + * @return the new id + */ + public static ObjectId get(){ + return new ObjectId(); + } + + /** Checks if a string could be an ObjectId. + * @return whether the string could be an object id + */ + public static boolean isValid( String s ){ + if ( s == null ) + return false; + + final int len = s.length(); + if ( len != 24 ) + return false; + + for ( int i=0; i= '0' && c <= '9' ) + continue; + if ( c >= 'a' && c <= 'f' ) + continue; + if ( c >= 'A' && c <= 'F' ) + continue; + + return false; + } + + return true; + } + + /** Turn an object into an ObjectId, if possible. + * Strings will be converted into ObjectIds, if possible, and ObjectIds will + * be cast and returned. Passing in null returns null. + * @param o the object to convert + * @return an ObjectId if it can be massaged, null otherwise + */ + public static ObjectId massageToObjectId( Object o ){ + if ( o == null ) + return null; + + if ( o instanceof ObjectId ) + return (ObjectId)o; + + if ( o instanceof String ){ + String s = o.toString(); + if ( isValid( s ) ) + return new ObjectId( s ); + } + + return null; + } + + public ObjectId( Date time ){ + this(time, _genmachine, _nextInc.getAndIncrement()); + } + + public ObjectId( Date time , int inc ){ + this( time , _genmachine , inc ); + } + + public ObjectId( Date time , int machine , int inc ){ + _time = (int)(time.getTime() / 1000); + _machine = machine; + _inc = inc; + _new = false; + } + + /** Creates a new instance from a string. + * @param s the string to convert + * @throws IllegalArgumentException if the string is not a valid id + */ + public ObjectId( String s ){ + this( s , false ); + } + + public ObjectId( String s , boolean babble ){ + + if ( ! isValid( s ) ) + throw new IllegalArgumentException( "invalid ObjectId [" + s + "]" ); + + if ( babble ) + s = babbleToMongod( s ); + + byte b[] = new byte[12]; + for ( int i=0; i=0; i-- ) + buf.append( _pos( b , i ) ); + for ( int i=11; i>=8; i-- ) + buf.append( _pos( b , i ) ); + + return buf.toString(); + } + + public String toString(){ + return toStringMongod(); + } + + int _compareUnsigned( int i , int j ){ + long li = 0xFFFFFFFFL; + li = i & li; + long lj = 0xFFFFFFFFL; + lj = j & lj; + long diff = li - lj; + if (diff < Integer.MIN_VALUE) + return Integer.MIN_VALUE; + if (diff > Integer.MAX_VALUE) + return Integer.MAX_VALUE; + return (int) diff; + } + + public int compareTo( ObjectId id ){ + if ( id == null ) + return -1; + + int x = _compareUnsigned( _time , id._time ); + if ( x != 0 ) + return x; + + x = _compareUnsigned( _machine , id._machine ); + if ( x != 0 ) + return x; + + return _compareUnsigned( _inc , id._inc ); + } + + public int getMachine(){ + return _machine; + } + + /** + * Gets the time of this ID, in milliseconds + */ + public long getTime(){ + return _time * 1000L; + } + + /** + * Gets the time of this ID, in seconds + */ + public int getTimeSecond(){ + return _time; + } + + public int getInc(){ + return _inc; + } + + public int _time(){ + return _time; + } + public int _machine(){ + return _machine; + } + public int _inc(){ + return _inc; + } + + public boolean isNew(){ + return _new; + } + + public void notNew(){ + _new = false; + } + + /** + * Gets the generated machine ID, identifying the machine / process / class loader + */ + public static int getGenMachineId() { + return _genmachine; + } + + /** + * Gets the current value of the auto increment + */ + public static int getCurrentInc() { + return _nextInc.get(); + } + + final int _time; + final int _machine; + final int _inc; + + boolean _new; + + public static int _flip( int x ){ + int z = 0; + z |= ( ( x << 24 ) & 0xFF000000 ); + z |= ( ( x << 8 ) & 0x00FF0000 ); + z |= ( ( x >> 8 ) & 0x0000FF00 ); + z |= ( ( x >> 24 ) & 0x000000FF ); + return z; + } + + private static AtomicInteger _nextInc = new AtomicInteger( (new java.util.Random()).nextInt() ); + + private static final int _genmachine; + static { + + try { + // build a 2-byte machine piece based on NICs info + int machinePiece; + { + try { + StringBuilder sb = new StringBuilder(); + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while ( e.hasMoreElements() ){ + NetworkInterface ni = e.nextElement(); + sb.append( ni.toString() ); + } + machinePiece = sb.toString().hashCode() << 16; + } catch (Throwable e) { + // exception sometimes happens with IBM JVM, use random + LOGGER.log(Level.WARNING, e.getMessage(), e); + machinePiece = (new Random().nextInt()) << 16; + } + LOGGER.fine( "machine piece post: " + Integer.toHexString( machinePiece ) ); + } + + // add a 2 byte process piece. It must represent not only the JVM but the class loader. + // Since static var belong to class loader there could be collisions otherwise + final int processPiece; + { + int processId = new java.util.Random().nextInt(); + try { + processId = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode(); + } + catch ( Throwable t ){ + } + + ClassLoader loader = ObjectId.class.getClassLoader(); + int loaderId = loader != null ? System.identityHashCode(loader) : 0; + + StringBuilder sb = new StringBuilder(); + sb.append(Integer.toHexString(processId)); + sb.append(Integer.toHexString(loaderId)); + processPiece = sb.toString().hashCode() & 0xFFFF; + LOGGER.fine( "process piece: " + Integer.toHexString( processPiece ) ); + } + + _genmachine = machinePiece | processPiece; + LOGGER.fine( "machine : " + Integer.toHexString( _genmachine ) ); + } + catch ( Exception e ){ + throw new RuntimeException( e ); + } + + } +} + diff --git a/src/com/massivecraft/mcore3/lib/bson/types/Symbol.java b/src/com/massivecraft/mcore3/lib/bson/types/Symbol.java new file mode 100644 index 00000000..8e2e43ac --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/Symbol.java @@ -0,0 +1,74 @@ +// Symbol.java + +/** + * Copyright (C) 2009 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.types; + +import java.io.Serializable; + +/** + * Class to hold a BSON symbol object, which is an interned string in Ruby + */ +public class Symbol implements Serializable { + + private static final long serialVersionUID = 1326269319883146072L; + + public Symbol(String s) { + _symbol = s; + } + + public String getSymbol(){ + return _symbol; + } + + /** + * Will compare equal to a String that is equal to the String that this holds + * @param o + * @return + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + + String otherSymbol; + if (o instanceof Symbol) { + otherSymbol = ((Symbol) o)._symbol; + } + else if (o instanceof String) { + otherSymbol = (String) o; + } + else { + return false; + } + + if (_symbol != null ? !_symbol.equals(otherSymbol) : otherSymbol != null) return false; + + return true; + } + + @Override + public int hashCode() { + return _symbol != null ? _symbol.hashCode() : 0; + } + + public String toString(){ + return _symbol; + } + + private final String _symbol; +} diff --git a/src/com/massivecraft/mcore3/lib/bson/types/package.html b/src/com/massivecraft/mcore3/lib/bson/types/package.html new file mode 100644 index 00000000..ed93a43b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/types/package.html @@ -0,0 +1,3 @@ + +

Contains classes implementing various BSON types.

+ diff --git a/src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java b/src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java new file mode 100644 index 00000000..e16cb4d2 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java @@ -0,0 +1,631 @@ +/** + * Copyright 2008 Atlassian Pty Ltd + * + * 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.massivecraft.mcore3.lib.bson.util; + +import static com.massivecraft.mcore3.lib.bson.util.Assertions.notNull; +import static java.util.Collections.unmodifiableCollection; +import static java.util.Collections.unmodifiableSet; + +import com.massivecraft.mcore3.lib.bson.util.annotations.GuardedBy; +import com.massivecraft.mcore3.lib.bson.util.annotations.ThreadSafe; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Abstract base class for COW {@link Map} implementations that delegate to an + * internal map. + * + * @param The key type + * @param The value type + * @param the internal {@link Map} or extension for things like sorted and + * navigable maps. + */ +@ThreadSafe +abstract class AbstractCopyOnWriteMap> implements ConcurrentMap, Serializable { + private static final long serialVersionUID = 4508989182041753878L; + + @GuardedBy("lock") + private volatile M delegate; + + // import edu.umd.cs.findbugs.annotations.@SuppressWarnings + private final transient Lock lock = new ReentrantLock(); + + // private final transient EntrySet entrySet = new EntrySet(); + // private final transient KeySet keySet = new KeySet(); + // private final transient Values values = new Values(); + // private final View.Type viewType; + private final View view; + + /** + * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to + * initialize the values. + * + * @param map the initial map to initialize with + * @param viewType for writable or read-only key, value and entrySet views + */ + protected > AbstractCopyOnWriteMap(final N map, final View.Type viewType) { + this.delegate = notNull("delegate", copy(notNull("map", map))); + this.view = notNull("viewType", viewType).get(this); + } + + /** + * Copy function, implemented by sub-classes. + * + * @param the map to copy and return. + * @param map the initial values of the newly created map. + * @return a new map. Will never be modified after construction. + */ + @GuardedBy("lock") + abstract > M copy(N map); + + // + // mutable operations + // + + public final void clear() { + lock.lock(); + try { + set(copy(Collections. emptyMap())); + } finally { + lock.unlock(); + } + } + + public final V remove(final Object key) { + lock.lock(); + try { + // short circuit if key doesn't exist + if (!delegate.containsKey(key)) { + return null; + } + final M map = copy(); + try { + return map.remove(key); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public boolean remove(final Object key, final Object value) { + lock.lock(); + try { + if (delegate.containsKey(key) && equals(value, delegate.get(key))) { + final M map = copy(); + map.remove(key); + set(map); + return true; + } else { + return false; + } + } finally { + lock.unlock(); + } + } + + public boolean replace(final K key, final V oldValue, final V newValue) { + lock.lock(); + try { + if (!delegate.containsKey(key) || !equals(oldValue, delegate.get(key))) { + return false; + } + final M map = copy(); + map.put(key, newValue); + set(map); + return true; + } finally { + lock.unlock(); + } + } + + public V replace(final K key, final V value) { + lock.lock(); + try { + if (!delegate.containsKey(key)) { + return null; + } + final M map = copy(); + try { + return map.put(key, value); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public final V put(final K key, final V value) { + lock.lock(); + try { + final M map = copy(); + try { + return map.put(key, value); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public V putIfAbsent(final K key, final V value) { + lock.lock(); + try { + if (!delegate.containsKey(key)) { + final M map = copy(); + try { + return map.put(key, value); + } finally { + set(map); + } + } + return delegate.get(key); + } finally { + lock.unlock(); + } + } + + public final void putAll(final Map t) { + lock.lock(); + try { + final M map = copy(); + map.putAll(t); + set(map); + } finally { + lock.unlock(); + } + } + + protected M copy() { + lock.lock(); + try { + return copy(delegate); + } finally { + lock.unlock(); + } + } + + @GuardedBy("lock") + protected void set(final M map) { + delegate = map; + } + + // + // Collection views + // + + public final Set> entrySet() { + return view.entrySet(); + } + + public final Set keySet() { + return view.keySet(); + } + + public final Collection values() { + return view.values(); + } + + // + // delegate operations + // + + public final boolean containsKey(final Object key) { + return delegate.containsKey(key); + } + + public final boolean containsValue(final Object value) { + return delegate.containsValue(value); + } + + public final V get(final Object key) { + return delegate.get(key); + } + + public final boolean isEmpty() { + return delegate.isEmpty(); + } + + public final int size() { + return delegate.size(); + } + + @Override + public final boolean equals(final Object o) { + return delegate.equals(o); + } + + @Override + public final int hashCode() { + return delegate.hashCode(); + } + + protected final M getDelegate() { + return delegate; + } + + @Override + public String toString() { + return delegate.toString(); + } + + // + // inner classes + // + + private class KeySet extends CollectionView implements Set { + + @Override + Collection getDelegate() { + return delegate.keySet(); + } + + // + // mutable operations + // + + public void clear() { + lock.lock(); + try { + final M map = copy(); + map.keySet().clear(); + set(map); + } finally { + lock.unlock(); + } + } + + public boolean remove(final Object o) { + return AbstractCopyOnWriteMap.this.remove(o) != null; + } + + public boolean removeAll(final Collection c) { + lock.lock(); + try { + final M map = copy(); + try { + return map.keySet().removeAll(c); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public boolean retainAll(final Collection c) { + lock.lock(); + try { + final M map = copy(); + try { + return map.keySet().retainAll(c); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + } + + private final class Values extends CollectionView { + + @Override + Collection getDelegate() { + return delegate.values(); + } + + public void clear() { + lock.lock(); + try { + final M map = copy(); + map.values().clear(); + set(map); + } finally { + lock.unlock(); + } + } + + public boolean remove(final Object o) { + lock.lock(); + try { + if (!contains(o)) { + return false; + } + final M map = copy(); + try { + return map.values().remove(o); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public boolean removeAll(final Collection c) { + lock.lock(); + try { + final M map = copy(); + try { + return map.values().removeAll(c); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public boolean retainAll(final Collection c) { + lock.lock(); + try { + final M map = copy(); + try { + return map.values().retainAll(c); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + } + + private class EntrySet extends CollectionView> implements Set> { + + @Override + Collection> getDelegate() { + return delegate.entrySet(); + } + + public void clear() { + lock.lock(); + try { + final M map = copy(); + map.entrySet().clear(); + set(map); + } finally { + lock.unlock(); + } + } + + public boolean remove(final Object o) { + lock.lock(); + try { + if (!contains(o)) { + return false; + } + final M map = copy(); + try { + return map.entrySet().remove(o); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public boolean removeAll(final Collection c) { + lock.lock(); + try { + final M map = copy(); + try { + return map.entrySet().removeAll(c); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + + public boolean retainAll(final Collection c) { + lock.lock(); + try { + final M map = copy(); + try { + return map.entrySet().retainAll(c); + } finally { + set(map); + } + } finally { + lock.unlock(); + } + } + } + + private static class UnmodifiableIterator implements Iterator { + private final Iterator delegate; + + public UnmodifiableIterator(final Iterator delegate) { + this.delegate = delegate; + } + + public boolean hasNext() { + return delegate.hasNext(); + } + + public T next() { + return delegate.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + protected static abstract class CollectionView implements Collection { + + abstract Collection getDelegate(); + + // + // delegate operations + // + + public final boolean contains(final Object o) { + return getDelegate().contains(o); + } + + public final boolean containsAll(final Collection c) { + return getDelegate().containsAll(c); + } + + public final Iterator iterator() { + return new UnmodifiableIterator(getDelegate().iterator()); + } + + public final boolean isEmpty() { + return getDelegate().isEmpty(); + } + + public final int size() { + return getDelegate().size(); + } + + public final Object[] toArray() { + return getDelegate().toArray(); + } + + public final T[] toArray(final T[] a) { + return getDelegate().toArray(a); + } + + @Override + public int hashCode() { + return getDelegate().hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return getDelegate().equals(obj); + } + + @Override + public String toString() { + return getDelegate().toString(); + } + + // + // unsupported operations + // + + public final boolean add(final E o) { + throw new UnsupportedOperationException(); + } + + public final boolean addAll(final Collection c) { + throw new UnsupportedOperationException(); + } + } + + private boolean equals(final Object o1, final Object o2) { + if (o1 == null) { + return o2 == null; + } + return o1.equals(o2); + } + + /** + * Provides access to the views of the underlying key, value and entry + * collections. + */ + public static abstract class View { + View() {} + + abstract Set keySet(); + + abstract Set> entrySet(); + + abstract Collection values(); + + /** + * The different types of {@link View} available + */ + public enum Type { + STABLE { + @Override + > View get(final AbstractCopyOnWriteMap host) { + return host.new Immutable(); + } + }, + LIVE { + @Override + > View get(final AbstractCopyOnWriteMap host) { + return host.new Mutable(); + } + }; + abstract > View get(AbstractCopyOnWriteMap host); + } + } + + final class Immutable extends View implements Serializable { + + private static final long serialVersionUID = -4158727180429303818L; + + @Override + public Set keySet() { + return unmodifiableSet(delegate.keySet()); + } + + @Override + public Set> entrySet() { + return unmodifiableSet(delegate.entrySet()); + } + + @Override + public Collection values() { + return unmodifiableCollection(delegate.values()); + } + } + + final class Mutable extends View implements Serializable { + + private static final long serialVersionUID = 1624520291194797634L; + + private final transient KeySet keySet = new KeySet(); + private final transient EntrySet entrySet = new EntrySet(); + private final transient Values values = new Values(); + + @Override + public Set keySet() { + return keySet; + } + + @Override + public Set> entrySet() { + return entrySet; + } + + @Override + public Collection values() { + return values; + } + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/Assertions.java b/src/com/massivecraft/mcore3/lib/bson/util/Assertions.java new file mode 100644 index 00000000..ab5efa6b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/Assertions.java @@ -0,0 +1,48 @@ +/** + * Copyright 2008 Atlassian Pty Ltd + * + * 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.massivecraft.mcore3.lib.bson.util; + +/** + * Design by contract assertions. + */ +public class Assertions { + public static T notNull(final String name, final T notNull) throws IllegalArgumentException { + if (notNull == null) { + throw new NullArgumentException(name); + } + return notNull; + } + + public static void isTrue(final String name, final boolean check) throws IllegalArgumentException { + if (!check) { + throw new IllegalArgumentException(name); + } + } + + // /CLOVER:OFF + private Assertions() {} + + // /CLOVER:ON + + static class NullArgumentException extends IllegalArgumentException { + private static final long serialVersionUID = 6178592463723624585L; + + NullArgumentException(final String name) { + super(name + " should not be null!"); + } + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java b/src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java new file mode 100644 index 00000000..7c895f3b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java @@ -0,0 +1,72 @@ +package com.massivecraft.mcore3.lib.bson.util; + +import static com.massivecraft.mcore3.lib.bson.util.CopyOnWriteMap.newHashMap; +import static java.util.Collections.unmodifiableList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentMap; + +class ClassAncestry { + + /** + * getAncestry + * + * Walks superclass and interface graph, superclasses first, then + * interfaces, to compute an ancestry list. Supertypes are visited left to + * right. Duplicates are removed such that no Class will appear in the list + * before one of its subtypes. + * + * Does not need to be synchronized, races are harmless as the Class graph + * does not change at runtime. + */ + public static List> getAncestry(Class c) { + final ConcurrentMap, List>> cache = getClassAncestryCache(); + while (true) { + List> cachedResult = cache.get(c); + if (cachedResult != null) { + return cachedResult; + } + cache.putIfAbsent(c, computeAncestry(c)); + } + } + + /** + * computeAncestry, starting with children and going back to parents + */ + private static List> computeAncestry(Class c) { + final List> result = new ArrayList>(); + result.add(Object.class); + computeAncestry(c, result); + Collections.reverse(result); + return unmodifiableList(new ArrayList>(result)); + } + + private static void computeAncestry(Class c, List> result) { + if ((c == null) || (c == Object.class)) { + return; + } + + // first interfaces (looks backwards but is not) + Class[] interfaces = c.getInterfaces(); + for (int i = interfaces.length - 1; i >= 0; i--) { + computeAncestry(interfaces[i], result); + } + + // next superclass + computeAncestry(c.getSuperclass(), result); + + if (!result.contains(c)) + result.add(c); + } + + /** + * classAncestryCache + */ + private static ConcurrentMap, List>> getClassAncestryCache() { + return (_ancestryCache); + } + + private static final ConcurrentMap, List>> _ancestryCache = newHashMap(); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java b/src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java new file mode 100644 index 00000000..a51c624f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java @@ -0,0 +1,100 @@ +// ClassMap.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.util; + +import java.util.List; +import java.util.Map; + +/** + * Maps Class objects to values. A ClassMap is different from a regular Map in + * that get(c) does not only look to see if 'c' is a key in the Map, but also + * walks the up superclass and interface graph of 'c' to find matches. Derived + * matches of this sort are then "cached" in the registry so that matches are + * faster on future gets. + * + * This is a very useful class for Class based registries. + * + * Example: + * + * ClassMap m = new ClassMap(); m.put(Animal.class, "Animal"); + * m.put(Fox.class, "Fox"); m.Fox.class) --> "Fox" m.get(Dog.class) --> "Animal" + * + * (assuming Dog.class < Animal.class) + */ +public class ClassMap { + /** + * Walks superclass and interface graph, superclasses first, then + * interfaces, to compute an ancestry list. Supertypes are visited left to + * right. Duplicates are removed such that no Class will appear in the list + * before one of its subtypes. + */ + public static List> getAncestry(Class c) { + return ClassAncestry.getAncestry(c); + } + + private final class ComputeFunction implements Function, T> { + @Override + public T apply(Class a) { + for (Class cls : getAncestry(a)) { + T result = map.get(cls); + if (result != null) { + return result; + } + } + return null; + } + }; + + private final Map, T> map = CopyOnWriteMap.newHashMap(); + private final Map, T> cache = ComputingMap.create(new ComputeFunction()); + + + public T get(Object key) { + return cache.get(key); + } + + public T put(Class key, T value) { + try { + return map.put(key, value); + } finally { + cache.clear(); + } + } + + public T remove(Object key) { + try { + return map.remove(key); + } finally { + cache.clear(); + } + } + + public void clear() { + map.clear(); + cache.clear(); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java b/src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java new file mode 100644 index 00000000..a3f1b621 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java @@ -0,0 +1,109 @@ +package com.massivecraft.mcore3.lib.bson.util; + +import static com.massivecraft.mcore3.lib.bson.util.Assertions.notNull; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +final class ComputingMap implements Map, Function { + + public static Map create(Function function) { + return new ComputingMap(CopyOnWriteMap. newHashMap(), function); + } + + private final ConcurrentMap map; + private final Function function; + + ComputingMap(ConcurrentMap map, Function function) { + this.map = notNull("map", map); + this.function = notNull("function", function); + } + + public V get(Object key) { + while (true) { + V v = map.get(key); + if (v != null) + return v; + @SuppressWarnings("unchecked") + K k = (K) key; + V value = function.apply(k); + if (value == null) + return null; + map.putIfAbsent(k, value); + } + } + + public V apply(K k) { + return get(k); + } + + public V putIfAbsent(K key, V value) { + return map.putIfAbsent(key, value); + } + + public boolean remove(Object key, Object value) { + return map.remove(key, value); + } + + public boolean replace(K key, V oldValue, V newValue) { + return map.replace(key, oldValue, newValue); + } + + public V replace(K key, V value) { + return map.replace(key, value); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public V put(K key, V value) { + return map.put(key, value); + } + + public V remove(Object key) { + return map.remove(key); + } + + public void putAll(Map m) { + map.putAll(m); + } + + public void clear() { + map.clear(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public boolean equals(Object o) { + return map.equals(o); + } + + public int hashCode() { + return map.hashCode(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java b/src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java new file mode 100644 index 00000000..271ee5a9 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java @@ -0,0 +1,273 @@ +/** + * Copyright 2008 Atlassian Pty Ltd + * + * 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.massivecraft.mcore3.lib.bson.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.WeakHashMap; + + +import com.massivecraft.mcore3.lib.bson.util.AbstractCopyOnWriteMap.View.Type; +import com.massivecraft.mcore3.lib.bson.util.annotations.GuardedBy; +import com.massivecraft.mcore3.lib.bson.util.annotations.ThreadSafe; + +/** + * A thread-safe variant of {@link Map} in which all mutative operations (the + * "destructive" operations described by {@link Map} put, remove and so on) are + * implemented by making a fresh copy of the underlying map. + *

+ * This is ordinarily too costly, but may be more efficient than + * alternatives when traversal operations vastly out-number mutations, and is + * useful when you cannot or don't want to synchronize traversals, yet need to + * preclude interference among concurrent threads. The "snapshot" style + * iterators on the collections returned by {@link #entrySet()}, + * {@link #keySet()} and {@link #values()} use a reference to the internal map + * at the point that the iterator was created. This map never changes during the + * lifetime of the iterator, so interference is impossible and the iterator is + * guaranteed not to throw ConcurrentModificationException. The + * iterators will not reflect additions, removals, or changes to the list since + * the iterator was created. Removing elements via these iterators is not + * supported. The mutable operations on these collections (remove, retain etc.) + * are supported but as with the {@link Map} interface, add and addAll are not + * and throw {@link UnsupportedOperationException}. + *

+ * The actual copy is performed by an abstract {@link #copy(Map)} method. The + * method is responsible for the underlying Map implementation (for instance a + * {@link HashMap}, {@link TreeMap}, {@link LinkedHashMap} etc.) and therefore + * the semantics of what this map will cope with as far as null keys and values, + * iteration ordering etc. See the note below about suitable candidates for + * underlying Map implementations + *

+ * There are supplied implementations for the common j.u.c {@link Map} + * implementations via the {@link CopyOnWriteMap} static {@link Builder}. + *

+ * Collection views of the keys, values and entries are optionally + * {@link View.Type.LIVE live} or {@link View.Type.STABLE stable}. Live views + * are modifiable will cause a copy if a modifying method is called on them. + * Methods on these will reflect the current state of the collection, although + * iterators will be snapshot style. If the collection views are stable they are + * unmodifiable, and will be a snapshot of the state of the map at the time the + * collection was asked for. + *

+ * Please note that the thread-safety guarantees are limited to + * the thread-safety of the non-mutative (non-destructive) operations of the + * underlying map implementation. For instance some implementations such as + * {@link WeakHashMap} and {@link LinkedHashMap} with access ordering are + * actually structurally modified by the {@link #get(Object)} method and are + * therefore not suitable candidates as delegates for this class. + * + * @param the key type + * @param the value type + * @author Jed Wesley-Smith + */ +@ThreadSafe +abstract class CopyOnWriteMap extends AbstractCopyOnWriteMap> { + private static final long serialVersionUID = 7935514534647505917L; + + /** + * Get a {@link Builder} for a {@link CopyOnWriteMap} instance. + * + * @param key type + * @param value type + * @return a fresh builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Build a {@link CopyOnWriteMap} and specify all the options. + * + * @param key type + * @param value type + */ + public static class Builder { + private View.Type viewType = View.Type.STABLE; + private final Map initialValues = new HashMap(); + + Builder() {} + + /** + * Views are stable (fixed in time) and unmodifiable. + */ + public Builder stableViews() { + viewType = View.Type.STABLE; + return this; + } + + /** + * Views are live (reflecting concurrent updates) and mutator methods + * are supported. + */ + public Builder addAll(final Map values) { + initialValues.putAll(values); + return this; + } + + /** + * Views are live (reflecting concurrent updates) and mutator methods + * are supported. + */ + public Builder liveViews() { + viewType = View.Type.LIVE; + return this; + } + + public CopyOnWriteMap newHashMap() { + return new Hash(initialValues, viewType); + } + + public CopyOnWriteMap newLinkedMap() { + return new Linked(initialValues, viewType); + } + } + + /** + * Creates a new {@link CopyOnWriteMap} with an underlying {@link HashMap}. + *

+ * This map has {@link View.Type.STABLE stable} views. + */ + public static CopyOnWriteMap newHashMap() { + final Builder builder = builder(); + return builder.newHashMap(); + } + + /** + * Creates a new {@link CopyOnWriteMap} with an underlying {@link HashMap} + * using the supplied map as the initial values. + *

+ * This map has {@link View.Type.STABLE stable} views. + */ + public static CopyOnWriteMap newHashMap(final Map map) { + final Builder builder = builder(); + return builder.addAll(map).newHashMap(); + } + + /** + * Creates a new {@link CopyOnWriteMap} with an underlying + * {@link LinkedHashMap}. Iterators for this map will be return elements in + * insertion order. + *

+ * This map has {@link View.Type.STABLE stable} views. + */ + public static CopyOnWriteMap newLinkedMap() { + final Builder builder = builder(); + return builder.newLinkedMap(); + } + + /** + * Creates a new {@link CopyOnWriteMap} with an underlying + * {@link LinkedHashMap} using the supplied map as the initial values. + * Iterators for this map will be return elements in insertion order. + *

+ * This map has {@link View.Type.STABLE stable} views. + */ + public static CopyOnWriteMap newLinkedMap(final Map map) { + final Builder builder = builder(); + return builder.addAll(map).newLinkedMap(); + } + + // + // constructors + // + + /** + * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to + * initialize the values. + * + * @param map the initial map to initialize with + * @deprecated since 0.0.12 use the versions that explicitly specify + * View.Type + */ + @Deprecated + protected CopyOnWriteMap(final Map map) { + this(map, View.Type.LIVE); + } + + /** + * Create a new empty {@link CopyOnWriteMap}. + * + * @deprecated since 0.0.12 use the versions that explicitly specify + * View.Type + */ + @Deprecated + protected CopyOnWriteMap() { + this(Collections. emptyMap(), View.Type.LIVE); + } + + /** + * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to + * initialize the values. This map may be optionally modified using any of + * the key, entry or value views + * + * @param map the initial map to initialize with + */ + protected CopyOnWriteMap(final Map map, final View.Type viewType) { + super(map, viewType); + } + + /** + * Create a new empty {@link CopyOnWriteMap}. This map may be optionally + * modified using any of the key, entry or value views + */ + protected CopyOnWriteMap(final View.Type viewType) { + super(Collections. emptyMap(), viewType); + } + + @Override + @GuardedBy("internal-lock") + protected abstract > Map copy(N map); + + // + // inner classes + // + + /** + * Uses {@link HashMap} instances as its internal storage. + */ + static class Hash extends CopyOnWriteMap { + private static final long serialVersionUID = 5221824943734164497L; + + Hash(final Map map, final Type viewType) { + super(map, viewType); + } + + @Override + public > Map copy(final N map) { + return new HashMap(map); + } + } + + /** + * Uses {@link LinkedHashMap} instances as its internal storage. + */ + static class Linked extends CopyOnWriteMap { + private static final long serialVersionUID = -8659999465009072124L; + + Linked(final Map map, final Type viewType) { + super(map, viewType); + } + + @Override + public > Map copy(final N map) { + return new LinkedHashMap(map); + } + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/Function.java b/src/com/massivecraft/mcore3/lib/bson/util/Function.java new file mode 100644 index 00000000..2405ed31 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/Function.java @@ -0,0 +1,5 @@ +package com.massivecraft.mcore3.lib.bson.util; + +interface Function { + B apply(A a); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java b/src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java new file mode 100644 index 00000000..e518831c --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java @@ -0,0 +1,58 @@ +// SimplePool.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.util; + +import java.util.*; +import java.util.concurrent.*; + +public abstract class SimplePool { + + public SimplePool( int max ){ + _max = max; + } + + public SimplePool(){ + _max = 1000; + } + + protected abstract T createNew(); + + protected boolean ok( T t ){ + return true; + } + + public T get(){ + T t = _stored.poll(); + if ( t != null ) + return t; + return createNew(); + } + + public void done( T t ){ + if ( ! ok( t ) ) + return; + + if ( _stored.size() > _max ) + return; + _stored.add( t ); + } + + final int _max; + private Queue _stored = new ConcurrentLinkedQueue(); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java b/src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java new file mode 100644 index 00000000..0cfec141 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2010 10gen Inc. + * + * 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.massivecraft.mcore3.lib.bson.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +public class StringRangeSet implements Set { + + private final int size; + + private final static int NUMSTR_LEN = 100; + private final static String[] NUMSTRS = new String[100]; + static { + for (int i = 0; i < NUMSTR_LEN; ++i) + NUMSTRS[i] = String.valueOf(i); + } + + public StringRangeSet(int size) { + this.size = size; + } + + public int size() { + return size; + } + + public Iterator iterator() { + return new Iterator() { + + int index = 0; + + public boolean hasNext() { + return index < size; + } + + public String next() { + if (index < NUMSTR_LEN) + return NUMSTRS[index++]; + return String.valueOf(index++); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public boolean add(String e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(Object o) { + int t = Integer.parseInt(String.valueOf(o)); + return t >= 0 && t < size; + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] toArray() { + String[] array = new String[size()]; + for (int i = 0; i < size; ++i) { + if (i < NUMSTR_LEN) { + array[i] = NUMSTRS[i]; + } else { + array[i] = String.valueOf(i); + } + } + return array; + } + + @Override + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java new file mode 100644 index 00000000..ac05e7c6 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2005 Brian Goetz and Tim Peierls + * Released under the Creative Commons Attribution License + * (http://creativecommons.org/licenses/by/2.5) + * Official home: http://www.jcip.net + * + * Any republication or derived work distributed in source code form + * must include this copyright and license notice. + */ + +package com.massivecraft.mcore3.lib.bson.util.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The field or method to which this annotation is applied can only be accessed + * when holding a particular lock, which may be a built-in (synchronization) lock, + * or may be an explicit java.util.concurrent.Lock. + * + * The argument determines which lock guards the annotated field or method: + *

    + *
  • + * this : The intrinsic lock of the object in whose class the field is defined. + *
  • + *
  • + * class-name.this : For inner classes, it may be necessary to disambiguate 'this'; + * the class-name.this designation allows you to specify which 'this' reference is intended + *
  • + *
  • + * itself : For reference fields only; the object to which the field refers. + *
  • + *
  • + * field-name : The lock object is referenced by the (instance or static) field + * specified by field-name. + *
  • + *
  • + * class-name.field-name : The lock object is reference by the static field specified + * by class-name.field-name. + *
  • + *
  • + * method-name() : The lock object is returned by calling the named nil-ary method. + *
  • + *
  • + * class-name.class : The Class object for the specified class should be used as the lock object. + *
  • + */ +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface GuardedBy { + String value(); +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java new file mode 100644 index 00000000..bf27f44c --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2005 Brian Goetz and Tim Peierls + * Released under the Creative Commons Attribution License + * (http://creativecommons.org/licenses/by/2.5) + * Official home: http://www.jcip.net + * + * Any republication or derived work distributed in source code form + * must include this copyright and license notice. + */ + +package com.massivecraft.mcore3.lib.bson.util.annotations; + +import java.lang.annotation.*; + +/** + * The class to which this annotation is applied is immutable. This means that + * its state cannot be seen to change by callers, which implies that + *
      + *
    • all public fields are final,
    • + *
    • all public final reference fields refer to other immutable objects, and
    • + *
    • constructors and methods do not publish references to any internal state + * which is potentially mutable by the implementation.
    • + *
    + * Immutable objects may still have internal mutable state for purposes of performance + * optimization; some state variables may be lazily computed, so long as they are computed + * from immutable state and that callers cannot tell the difference. + *

    + * Immutable objects are inherently thread-safe; they may be passed between threads or + * published without synchronization. + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Immutable { +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java new file mode 100644 index 00000000..1b229bed --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2005 Brian Goetz and Tim Peierls + * Released under the Creative Commons Attribution License + * (http://creativecommons.org/licenses/by/2.5) + * Official home: http://www.jcip.net + * + * Any republication or derived work distributed in source code form + * must include this copyright and license notice. + */ + +package com.massivecraft.mcore3.lib.bson.util.annotations; + +import java.lang.annotation.*; + + +/** + * The class to which this annotation is applied is not thread-safe. + * This annotation primarily exists for clarifying the non-thread-safety of a class + * that might otherwise be assumed to be thread-safe, despite the fact that it is a bad + * idea to assume a class is thread-safe without good reason. + * @see ThreadSafe + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface NotThreadSafe { +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java new file mode 100644 index 00000000..3b2cbd6c --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2005 Brian Goetz and Tim Peierls + * Released under the Creative Commons Attribution License + * (http://creativecommons.org/licenses/by/2.5) + * Official home: http://www.jcip.net + * + * Any republication or derived work distributed in source code form + * must include this copyright and license notice. + */ + +package com.massivecraft.mcore3.lib.bson.util.annotations; + +import java.lang.annotation.*; + + +/** + * The class to which this annotation is applied is thread-safe. This means that + * no sequences of accesses (reads and writes to public fields, calls to public methods) + * may put the object into an invalid state, regardless of the interleaving of those actions + * by the runtime, and without requiring any additional synchronization or coordination on the + * part of the caller. + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ThreadSafe { +} diff --git a/src/com/massivecraft/mcore3/lib/bson/util/package.html b/src/com/massivecraft/mcore3/lib/bson/util/package.html new file mode 100644 index 00000000..50f680e3 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/bson/util/package.html @@ -0,0 +1,3 @@ + +

    Misc utils used by BSON.

    + diff --git a/src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java new file mode 100644 index 00000000..87197c9d --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java @@ -0,0 +1,67 @@ +// BasicDBList.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + + +import com.massivecraft.mcore3.lib.bson.types.BasicBSONList; +import com.massivecraft.mcore3.lib.mongodb.util.JSON; + +/** + * a basic implementation of bson list that is mongo specific + * @author antoine + */ +public class BasicDBList extends BasicBSONList implements DBObject { + + private static final long serialVersionUID = -4415279469780082174L; + + /** + * Returns a JSON serialization of this object + * @return JSON serialization + */ + @Override + public String toString(){ + return JSON.serialize( this ); + } + + public boolean isPartialObject(){ + return _isPartialObject; + } + + public void markAsPartialObject(){ + _isPartialObject = true; + } + + public Object copy() { + // copy field values into new object + BasicDBList newobj = new BasicDBList(); + // need to clone the sub obj + for (int i = 0; i < size(); ++i) { + Object val = get(i); + if (val instanceof BasicDBObject) { + val = ((BasicDBObject)val).copy(); + } else if (val instanceof BasicDBList) { + val = ((BasicDBList)val).copy(); + } + newobj.add(val); + } + return newobj; + } + + private boolean _isPartialObject; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java new file mode 100644 index 00000000..e8b8c161 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java @@ -0,0 +1,110 @@ +// BasicDBObject.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.Map; + + +import com.massivecraft.mcore3.lib.bson.BasicBSONObject; +import com.massivecraft.mcore3.lib.mongodb.util.JSON; + +/** + * a basic implementation of bson object that is mongo specific. + * A DBObject can be created as follows, using this class: + *
    + * DBObject obj = new BasicDBObject();
    + * obj.put( "foo", "bar" );
    + * 
    + */ +public class BasicDBObject extends BasicBSONObject implements DBObject { + + private static final long serialVersionUID = -4415279469780082174L; + + /** + * Creates an empty object. + */ + public BasicDBObject(){ + } + + /** + * creates an empty object + * @param size an estimate of number of fields that will be inserted + */ + public BasicDBObject(int size){ + super(size); + } + + /** + * creates an object with the given key/value + * @param key key under which to store + * @param value value to stor + */ + public BasicDBObject(String key, Object value){ + super(key, value); + } + + /** + * Creates an object from a map. + * @param m map to convert + */ + @SuppressWarnings("rawtypes") + public BasicDBObject(Map m) { + super(m); + } + + public boolean isPartialObject(){ + return _isPartialObject; + } + + public void markAsPartialObject(){ + _isPartialObject = true; + } + + /** + * Returns a JSON serialization of this object + * @return JSON serialization + */ + @Override + public String toString(){ + return JSON.serialize( this ); + } + + @Override + public BasicDBObject append( String key , Object val ){ + put( key , val ); + return this; + } + + public Object copy() { + // copy field values into new object + BasicDBObject newobj = new BasicDBObject(this.toMap()); + // need to clone the sub obj + for (String field : keySet()) { + Object val = get(field); + if (val instanceof BasicDBObject) { + newobj.put(field, ((BasicDBObject)val).copy()); + } else if (val instanceof BasicDBList) { + newobj.put(field, ((BasicDBList)val).copy()); + } + } + return newobj; + } + + private boolean _isPartialObject = false; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java new file mode 100644 index 00000000..28f2b8dc --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java @@ -0,0 +1,142 @@ +// BasicDBObjectBuilder.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; + +/** + * utility for building complex objects + * example: + * BasicDBObjectBuilder.start().add( "name" , "eliot" ).add( "number" , 17 ).get() + */ +public class BasicDBObjectBuilder { + + /** + * creates an empty object + */ + public BasicDBObjectBuilder(){ + _stack = new LinkedList(); + _stack.add( new BasicDBObject() ); + } + + /** + * Creates an empty object + * @return The new empty builder + */ + public static BasicDBObjectBuilder start(){ + return new BasicDBObjectBuilder(); + } + + /** + * creates an object with the given key/value + * @param k The field name + * @param val The value + */ + public static BasicDBObjectBuilder start( String k , Object val ){ + return (new BasicDBObjectBuilder()).add( k , val ); + } + + /** + * Creates an object builder from an existing map. + * @param m map to use + * @return the new builder + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static BasicDBObjectBuilder start(Map m){ + BasicDBObjectBuilder b = new BasicDBObjectBuilder(); + Iterator i = m.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = i.next(); + b.add(entry.getKey().toString(), entry.getValue()); + } + return b; + } + + /** + * appends the key/value to the active object + * @param key + * @param val + * @return returns itself so you can chain + */ + public BasicDBObjectBuilder append( String key , Object val ){ + _cur().put( key , val ); + return this; + } + + + /** + * same as appends + * @see #append(String, Object) + * @param key + * @param val + * @return returns itself so you can chain + */ + public BasicDBObjectBuilder add( String key , Object val ){ + return append( key, val ); + } + + /** + * creates an new empty object and inserts it into the current object with the given key. + * The new child object becomes the active one. + * @param key + * @return returns itself so you can chain + */ + public BasicDBObjectBuilder push( String key ){ + BasicDBObject o = new BasicDBObject(); + _cur().put( key , o ); + _stack.addLast( o ); + return this; + } + + /** + * pops the active object, which means that the parent object becomes active + * @return returns itself so you can chain + */ + public BasicDBObjectBuilder pop(){ + if ( _stack.size() <= 1 ) + throw new IllegalArgumentException( "can't pop last element" ); + _stack.removeLast(); + return this; + } + + /** + * gets the base object + * @return The base object + */ + public DBObject get(){ + return _stack.getFirst(); + } + + /** + * returns true if no key/value was inserted into base object + * @return True if empty + */ + public boolean isEmpty(){ + return ((BasicDBObject) _stack.getFirst()).size() == 0; + } + + private DBObject _cur(){ + return _stack.getLast(); + } + + private final LinkedList _stack; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/Bytes.java b/src/com/massivecraft/mcore3/lib/mongodb/Bytes.java new file mode 100644 index 00000000..68fa6667 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/Bytes.java @@ -0,0 +1,227 @@ +// Bytes.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.nio.ByteOrder; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.massivecraft.mcore3.lib.bson.BSON; +import com.massivecraft.mcore3.lib.bson.types.BSONTimestamp; +import com.massivecraft.mcore3.lib.bson.types.Code; +import com.massivecraft.mcore3.lib.bson.types.CodeWScope; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +/** + * Class that hold definitions of the wire protocol + * @author antoine + */ +public class Bytes extends BSON { + + static final Logger LOGGER = Logger.getLogger( "com.mongodb" ); + + static final boolean D = Boolean.getBoolean( "DEBUG.MONGO" ); + + static { + if ( LOGGER.getLevel() == null ){ + if ( D ) + LOGGER.setLevel( Level.ALL ); + else + LOGGER.setLevel( Level.WARNING ); + } + } + + /** Little-endian */ + public static final ByteOrder ORDER = ByteOrder.LITTLE_ENDIAN; + + /** this size is set low to 4MB, but just serves as safe default */ + static final int MAX_OBJECT_SIZE = 1024 * 1024 * 4; + + /** default target size of an insert batch */ + static final int BATCH_INSERT_SIZE = 1024 * 1024 * 8; + + static final int CONNECTIONS_PER_HOST = Integer.parseInt( System.getProperty( "MONGO.POOLSIZE" , "10" ) ); + + + // --- network protocol options + + /** + * Tailable means cursor is not closed when the last data is retrieved. + * Rather, the cursor marks the final object's position. + * You can resume using the cursor later, from where it was located, if more data were received. + * Like any "latent cursor", the cursor may become invalid at some point (CursorNotFound) – for example if the final object it references were deleted. + */ + public static final int QUERYOPTION_TAILABLE = 1 << 1; + /** + * When turned on, read queries will be directed to slave servers instead of the primary server. + */ + public static final int QUERYOPTION_SLAVEOK = 1 << 2; + /** + * Internal replication use only - driver should not set + */ + public static final int QUERYOPTION_OPLOGREPLAY = 1 << 3; + /** + * The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use. + * Set this option to prevent that. + */ + public static final int QUERYOPTION_NOTIMEOUT = 1 << 4; + + /** + * Use with TailableCursor. + * If we are at the end of the data, block for a while rather than returning no data. + * After a timeout period, we do return as normal. + */ + public static final int QUERYOPTION_AWAITDATA = 1 << 5; + + /** + * Stream the data down full blast in multiple "more" packages, on the assumption that the client will fully read all data queried. + * Faster when you are pulling a lot of data and know you want to pull it all down. + * Note: the client is not allowed to not read all the data unless it closes the connection. + */ + public static final int QUERYOPTION_EXHAUST = 1 << 6; + + /** + * Use with sharding (mongos). + * Allows partial results from a sharded system if any shards are down/missing from the cluster. If not used an error will be returned + * from the mongos server. + */ + public static final int QUERYOPTION_PARTIAL = 1 << 7; + + /** + * Set when getMore is called but the cursor id is not valid at the server. + * Returned with zero results. + */ + public static final int RESULTFLAG_CURSORNOTFOUND = 1; + /** + * Set when query failed. + * Results consist of one document containing an "$err" field describing the failure. + */ + public static final int RESULTFLAG_ERRSET = 2; + /** + * Drivers should ignore this. + * Only mongos will ever see this set, in which case, it needs to update config from the server. + */ + public static final int RESULTFLAG_SHARDCONFIGSTALE = 4; + /** + * Set when the server supports the AwaitData Query option. + * If it doesn't, a client should sleep a little between getMore's of a Tailable cursor. + * Mongod version 1.6 supports AwaitData and thus always sets AwaitCapable. + */ + public static final int RESULTFLAG_AWAITCAPABLE = 8; + + + static class OptionHolder { + OptionHolder( OptionHolder parent ){ + _parent = parent; + } + + void set( int options ){ + _options = options; + _hasOptions = true; + } + + int get(){ + if ( _hasOptions ) + return _options; + if ( _parent == null ) + return 0; + return _parent.get(); + } + + void add( int option ){ + set( get() | option ); + } + + void reset(){ + _hasOptions = false; + } + + final OptionHolder _parent; + + int _options = 0; + boolean _hasOptions = false; + } + + /** + * Gets the type byte for a given object. + * @param o the object + * @return the byte value associated with the type, or -1 if no type is matched + */ + @SuppressWarnings("deprecation") + public static byte getType( Object o ){ + if ( o == null ) + return NULL; + + if ( o instanceof DBPointer ) + return REF; + + if (o instanceof Integer + || o instanceof Short + || o instanceof Byte + || o instanceof AtomicInteger) { + return NUMBER_INT; + } + + if (o instanceof Long || o instanceof AtomicLong) { + return NUMBER_LONG; + } + + if ( o instanceof Number ) + return NUMBER; + + if ( o instanceof String ) + return STRING; + + if ( o instanceof java.util.List ) + return ARRAY; + + if ( o instanceof byte[] ) + return BINARY; + + if ( o instanceof ObjectId ) + return OID; + + if ( o instanceof Boolean ) + return BOOLEAN; + + if ( o instanceof java.util.Date ) + return DATE; + + if ( o instanceof BSONTimestamp ) + return TIMESTAMP; + + if ( o instanceof java.util.regex.Pattern ) + return REGEX; + + if ( o instanceof DBObject || o instanceof DBRefBase ) + return OBJECT; + + if ( o instanceof Code ) + return CODE; + + if ( o instanceof CodeWScope ) + return CODE_W_SCOPE; + + return -1; + } + + static final ObjectId COLLECTION_REF_ID = new ObjectId( -1 , -1 , -1 ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java b/src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java new file mode 100644 index 00000000..79f0989f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java @@ -0,0 +1,153 @@ +// CommandResult.java +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + + +/** + * A simple wrapper for the result of getLastError() calls and other commands + */ +public class CommandResult extends BasicDBObject { + + CommandResult(ServerAddress srv) { + this(null, srv); + } + + CommandResult(DBObject cmd, ServerAddress srv) { + if (srv == null) { + throw new IllegalArgumentException("server address is null"); + } + _cmd = cmd; + _host = srv; + //so it is shown in toString/debug + put("serverUsed", srv.toString()); + } + + /** + * gets the "ok" field which is the result of the command + * @return True if ok + */ + public boolean ok(){ + Object o = get( "ok" ); + if ( o == null ) + throw new IllegalArgumentException( "'ok' should never be null..." ); + + if ( o instanceof Boolean ) + return (Boolean) o; + + if ( o instanceof Number ) + return ((Number)o).intValue() == 1; + + throw new IllegalArgumentException( "can't figure out what to do with: " + o.getClass().getName() ); + } + + /** + * gets the "errmsg" field which holds the error message + * @return The error message or null + */ + public String getErrorMessage(){ + Object foo = get( "errmsg" ); + if ( foo == null ) + return null; + return foo.toString(); + } + + /** + * utility method to create an exception with the command name + * @return The mongo exception or null + */ + public MongoException getException(){ + if ( !ok() ) { + StringBuilder buf = new StringBuilder(); + + String cmdName; + if (_cmd != null) { + cmdName = _cmd.keySet().iterator().next(); + buf.append( "command failed [" ).append( cmdName ).append( "]: " ); + } else { + buf.append( "operation failed: "); + } + + buf.append( toString() ); + + return new CommandFailure( this , buf.toString() ); + } else { + // GLE check + if ( hasErr() ) { + Object foo = get( "err" ); + + int code = getCode(); + + String s = foo.toString(); + if ( code == 11000 || code == 11001 || s.startsWith( "E11000" ) || s.startsWith( "E11001" ) ) + return new MongoException.DuplicateKey( code , s ); + + return new MongoException( code , s ); + } + } + + //all good, should never get here. + return null; + } + + /** + * returns the "code" field, as an int + * @return -1 if there is no code + */ + private int getCode(){ + int code = -1; + if ( get( "code" ) instanceof Number ) + code = ((Number)get("code")).intValue(); + return code; + } + + /** + * check the "err" field + * @return if it has it, and isn't null + */ + boolean hasErr(){ + Object o = get( "err" ); + return (o != null && ( (String) o ).length() > 0 ); + } + + /** + * throws an exception containing the cmd name, in case the command failed, or the "err/code" information + * @throws MongoException + */ + public void throwOnError() throws MongoException { + if ( !ok() || hasErr() ){ + throw getException(); + } + } + + public ServerAddress getServerUsed() { + return _host; + } + + private final DBObject _cmd; + private final ServerAddress _host; + private static final long serialVersionUID = 1L; + + static class CommandFailure extends MongoException { + private static final long serialVersionUID = 1L; + + CommandFailure( CommandResult res , String msg ){ + super( ServerError.getCode( res ) , msg ); + } + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DB.java b/src/com/massivecraft/mcore3/lib/mongodb/DB.java new file mode 100644 index 00000000..55ffe0c9 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DB.java @@ -0,0 +1,729 @@ +// DB.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.massivecraft.mcore3.lib.mongodb.DBApiLayer.Result; +import com.massivecraft.mcore3.lib.mongodb.util.Util; + +/** + * an abstract class that represents a logical database on a server + * @dochub databases + */ +public abstract class DB { + + /** + * @param mongo the mongo instance + * @param name the database name + */ + public DB( Mongo mongo , String name ){ + _mongo = mongo; + _name = name; + _options = new Bytes.OptionHolder( _mongo._netOptions ); + } + + /** + * starts a new "consistent request". + * Following this call and until requestDone() is called, all db operations should use the same underlying connection. + * This is useful to ensure that operations happen in a certain order with predictable results. + */ + public abstract void requestStart(); + + /** + * ends the current "consistent request" + */ + public abstract void requestDone(); + + /** + * ensure that a connection is assigned to the current "consistent request" (from primary pool, if connected to a replica set) + */ + public abstract void requestEnsureConnection(); + + /** + * Returns the collection represented by the string <dbName>.<collectionName>. + * @param name the name of the collection + * @return the collection + */ + protected abstract DBCollection doGetCollection( String name ); + + /** + * Gets a collection with a given name. + * If the collection does not exist, a new collection is created. + * @param name the name of the collection to return + * @return the collection + */ + public DBCollection getCollection( String name ){ + DBCollection c = doGetCollection( name ); + return c; + } + + /** + * Creates a collection with a given name and options. + * If the collection does not exist, a new collection is created. + * Note that if the options parameter is null, the creation will be deferred to when the collection is written to. + * Possible options: + *
    + *
    capped
    boolean: if the collection is capped
    + *
    size
    int: collection size (in bytes)
    + *
    max
    int: max number of documents
    + *
    + * @param name the name of the collection to return + * @param options options + * @return the collection + */ + public DBCollection createCollection( String name, DBObject options ){ + if ( options != null ){ + DBObject createCmd = new BasicDBObject("create", name); + createCmd.putAll(options); + CommandResult result = command(createCmd); + result.throwOnError(); + } + return getCollection(name); + } + + + /** + * Returns a collection matching a given string. + * @param s the name of the collection + * @return the collection + */ + public DBCollection getCollectionFromString( String s ){ + DBCollection foo = null; + + int idx = s.indexOf( "." ); + while ( idx >= 0 ){ + String b = s.substring( 0 , idx ); + s = s.substring( idx + 1 ); + if ( foo == null ) + foo = getCollection( b ); + else + foo = foo.getCollection( b ); + idx = s.indexOf( "." ); + } + + if ( foo != null ) + return foo.getCollection( s ); + return getCollection( s ); + } + + /** + * Executes a database command. + * This method calls {@link DB#command(com.massivecraft.mcore3.lib.mongodb.DBObject, int) } with 0 as query option. + * @see List of Commands + * @param cmd dbobject representing the command to execute + * @return result of command from the database + * @throws MongoException + * @dochub commands + */ + public CommandResult command( DBObject cmd ) throws MongoException{ + return command( cmd, 0 ); + } + + public CommandResult command( DBObject cmd, DBEncoder encoder ) throws MongoException{ + return command( cmd, 0, encoder ); + } + + public CommandResult command( DBObject cmd , int options, DBEncoder encoder ) + throws MongoException { + return command(cmd, options, null, encoder); + } + + public CommandResult command( DBObject cmd , int options, ReadPreference readPrefs ) + throws MongoException { + return command(cmd, options, readPrefs, DefaultDBEncoder.FACTORY.create()); + } + + /** + * Executes a database command. + * @see List of Commands + * @param cmd dbobject representing the command to execute + * @param options query options to use + * @param readPrefs ReadPreferences for this command (nodes selection is the biggest part of this) + * @return result of command from the database + * @dochub commands + * @throws MongoException + */ + public CommandResult command( DBObject cmd , int options, ReadPreference readPrefs, DBEncoder encoder ) + throws MongoException { + + Iterator i = + getCollection("$cmd").__find(cmd, new BasicDBObject(), 0, -1, 0, options, readPrefs , + DefaultDBDecoder.FACTORY.create(), encoder); + if ( i == null || ! i.hasNext() ) + return null; + + DBObject res = i.next(); + ServerAddress sa = (i instanceof Result) ? ((Result) i).getServerAddress() : null; + CommandResult cr = new CommandResult(cmd, sa); + cr.putAll( res ); + return cr; + } + + /** + * Executes a database command. + * @see List of Commands + * @param cmd dbobject representing the command to execute + * @param options query options to use + * @return result of command from the database + * @dochub commands + * @throws MongoException + */ + public CommandResult command( DBObject cmd , int options ) + throws MongoException { + return command(cmd, options, getReadPreference()); + } + /** + * Executes a database command. + * This method constructs a simple dbobject and calls {@link DB#command(com.massivecraft.mcore3.lib.mongodb.DBObject) } + * @see List of Commands + * @param cmd command to execute + * @return result of command from the database + * @throws MongoException + */ + public CommandResult command( String cmd ) + throws MongoException { + return command( new BasicDBObject( cmd , Boolean.TRUE ) ); + } + + /** + * Executes a database command. + * This method constructs a simple dbobject and calls {@link DB#command(com.massivecraft.mcore3.lib.mongodb.DBObject, int) } + * @see List of Commands + * @param cmd command to execute + * @param options query options to use + * @return result of command from the database + * @throws MongoException + */ + public CommandResult command( String cmd, int options ) + throws MongoException { + return command( new BasicDBObject( cmd , Boolean.TRUE ), options ); + } + + /** + * evaluates a function on the database. + * This is useful if you need to touch a lot of data lightly, in which case network transfer could be a bottleneck. + * @param code the function in javascript code + * @param args arguments to be passed to the function + * @return The command result + * @throws MongoException + */ + public CommandResult doEval( String code , Object ... args ) + throws MongoException { + + return command( BasicDBObjectBuilder.start() + .add( "$eval" , code ) + .add( "args" , args ) + .get() ); + } + + /** + * calls {@link DB#doEval(java.lang.String, java.lang.Object[]) }. + * If the command is successful, the "retval" field is extracted and returned. + * Otherwise an exception is thrown. + * @param code the function in javascript code + * @param args arguments to be passed to the function + * @return The object + * @throws MongoException + */ + public Object eval( String code , Object ... args ) + throws MongoException { + + CommandResult res = doEval( code , args ); + res.throwOnError(); + return res.get( "retval" ); + } + + /** + * Returns the result of "dbstats" command + * @return + */ + public CommandResult getStats() { + return command("dbstats"); + } + + /** + * Returns the name of this database. + * @return the name + */ + public String getName(){ + return _name; + } + + /** + * Makes this database read-only. + * Important note: this is a convenience setting that is only known on the client side and not persisted. + * @param b if the database should be read-only + */ + public void setReadOnly( Boolean b ){ + _readOnly = b; + } + + /** + * Returns a set containing the names of all collections in this database. + * @return the names of collections in this database + * @throws MongoException + */ + public Set getCollectionNames() + throws MongoException { + + DBCollection namespaces = getCollection("system.namespaces"); + if (namespaces == null) + throw new RuntimeException("this is impossible"); + + Iterator i = namespaces.__find(new BasicDBObject(), null, 0, 0, 0, getOptions(), getReadPreference(), null); + if (i == null) + return new HashSet(); + + List tables = new ArrayList(); + + for (; i.hasNext();) { + DBObject o = i.next(); + if ( o.get( "name" ) == null ){ + throw new MongoException( "how is name null : " + o ); + } + String n = o.get("name").toString(); + int idx = n.indexOf("."); + + String root = n.substring(0, idx); + if (!root.equals(_name)) + continue; + + if (n.indexOf("$") >= 0) + continue; + + String table = n.substring(idx + 1); + + tables.add(table); + } + + Collections.sort(tables); + + return new LinkedHashSet(tables); + } + + /** + * Checks to see if a collection by name %lt;name> exists. + * @param collectionName The collection to test for existence + * @return false if no collection by that name exists, true if a match to an existing collection was found + */ + public boolean collectionExists(String collectionName) + { + if (collectionName == null || "".equals(collectionName)) + return false; + + Set collections = getCollectionNames(); + if (collections.isEmpty()) + return false; + + for (String collection : collections) + { + if (collectionName.equalsIgnoreCase(collection)) + return true; + } + + return false; + } + + + /** + * Returns the name of this database. + * @return the name + */ + @Override + public String toString(){ + return _name; + } + + /** + * Gets the the error (if there is one) from the previous operation on this connection. + * The result of this command will look like + * + *
    +     * { "err" :  errorMessage  , "ok" : 1.0 }
    +     * 
    + * + * The value for errorMessage will be null if no error occurred, or a description otherwise. + * + * Important note: when calling this method directly, it is undefined which connection "getLastError" is called on. + * You may need to explicitly use a "consistent Request", see {@link DB#requestStart()} + * For most purposes it is better not to call this method directly but instead use {@link WriteConcern} + * + * @return DBObject with error and status information + * @throws MongoException + */ + public CommandResult getLastError() + throws MongoException { + return command(new BasicDBObject("getlasterror", 1)); + } + + /** + * @see {@link DB#getLastError() } + * @param concern the concern associated with "getLastError" call + * @return + * @throws MongoException + */ + public CommandResult getLastError( com.massivecraft.mcore3.lib.mongodb.WriteConcern concern ) + throws MongoException { + return command( concern.getCommand() ); + } + + /** + * @see {@link DB#getLastError(com.massivecraft.mcore3.lib.mongodb.WriteConcern) } + * @param w + * @param wtimeout + * @param fsync + * @return The command result + * @throws MongoException + */ + public CommandResult getLastError( int w , int wtimeout , boolean fsync ) + throws MongoException { + return command( (new com.massivecraft.mcore3.lib.mongodb.WriteConcern( w, wtimeout , fsync )).getCommand() ); + } + + + /** + * Sets the write concern for this database. It Will be used for + * writes to any collection in this database. See the + * documentation for {@link WriteConcern} for more information. + * @param concern write concern to use + */ + public void setWriteConcern( com.massivecraft.mcore3.lib.mongodb.WriteConcern concern ){ + if (concern == null) throw new IllegalArgumentException(); + _concern = concern; + } + + /** + * Gets the write concern for this database. + * @return + */ + public com.massivecraft.mcore3.lib.mongodb.WriteConcern getWriteConcern(){ + if ( _concern != null ) + return _concern; + return _mongo.getWriteConcern(); + } + + /** + * Sets the read preference for this database. Will be used as default for + * reads from any collection in this database. See the + * documentation for {@link ReadPreference} for more information. + * + * @param preference Read Preference to use + */ + public void setReadPreference( ReadPreference preference ){ + _readPref = preference; + } + + /** + * Gets the default read preference + * @return + */ + public ReadPreference getReadPreference(){ + if ( _readPref != null ) + return _readPref; + return _mongo.getReadPreference(); + } + + /** + * Drops this database. Removes all data on disk. Use with caution. + * @throws MongoException + */ + public void dropDatabase() + throws MongoException { + + CommandResult res = command(new BasicDBObject("dropDatabase", 1)); + res.throwOnError(); + _mongo._dbs.remove(this.getName()); + } + + /** + * Returns true if a user has been authenticated + * + * @return true if authenticated, false otherwise + * @dochub authenticate + */ + public boolean isAuthenticated() { + return ( _username != null ); + } + + /** + * Authenticates to db with the given name and password + * + * @param username name of user for this database + * @param passwd password of user for this database + * @return true if authenticated, false otherwise + * @throws MongoException + * @dochub authenticate + */ + public boolean authenticate(String username, char[] passwd ) + throws MongoException { + + if ( username == null || passwd == null ) + throw new NullPointerException( "username can't be null" ); + + if ( _username != null ) + throw new IllegalStateException( "can't call authenticate twice on the same DBObject" ); + + String hash = _hash( username , passwd ); + CommandResult res = _doauth( username , hash.getBytes() ); + if ( !res.ok()) + return false; + _username = username; + _authhash = hash.getBytes(); + return true; + } + + /** + * Authenticates to db with the given name and password + * + * @param username name of user for this database + * @param passwd password of user for this database + * @return the CommandResult from authenticate command + * @throws MongoException if authentication failed due to invalid user/pass, or other exceptions like I/O + * @dochub authenticate + */ + public CommandResult authenticateCommand(String username, char[] passwd ) + throws MongoException { + + if ( username == null || passwd == null ) + throw new NullPointerException( "username can't be null" ); + + if ( _username != null ) + throw new IllegalStateException( "can't call authenticate twice on the same DBObject" ); + + String hash = _hash( username , passwd ); + CommandResult res = _doauth( username , hash.getBytes() ); + res.throwOnError(); + _username = username; + _authhash = hash.getBytes(); + return res; + } + + /* + boolean reauth(){ + if ( _username == null || _authhash == null ) + throw new IllegalStateException( "no auth info!" ); + return _doauth( _username , _authhash ); + } + */ + + DBObject _authCommand( String nonce ){ + if ( _username == null || _authhash == null ) + throw new IllegalStateException( "no auth info!" ); + + return _authCommand( nonce , _username , _authhash ); + } + + static DBObject _authCommand( String nonce , String username , byte[] hash ){ + String key = nonce + username + new String( hash ); + + BasicDBObject cmd = new BasicDBObject(); + + cmd.put("authenticate", 1); + cmd.put("user", username); + cmd.put("nonce", nonce); + cmd.put("key", Util.hexMD5(key.getBytes())); + + return cmd; + } + + private CommandResult _doauth( String username , byte[] hash ){ + CommandResult res = command(new BasicDBObject("getnonce", 1)); + res.throwOnError(); + + DBObject cmd = _authCommand( res.getString( "nonce" ) , username , hash ); + return command(cmd); + } + + /** + * Adds a new user for this db + * @param username + * @param passwd + */ + public WriteResult addUser( String username , char[] passwd ){ + return addUser(username, passwd, false); + } + + /** + * Adds a new user for this db + * @param username + * @param passwd + * @param readOnly if true, user will only be able to read + */ + public WriteResult addUser( String username , char[] passwd, boolean readOnly ){ + DBCollection c = getCollection( "system.users" ); + DBObject o = c.findOne( new BasicDBObject( "user" , username ) ); + if ( o == null ) + o = new BasicDBObject( "user" , username ); + o.put( "pwd" , _hash( username , passwd ) ); + o.put( "readOnly" , readOnly ); + return c.save( o ); + } + + /** + * Removes a user for this db + * @param username + */ + public WriteResult removeUser( String username ){ + DBCollection c = getCollection( "system.users" ); + return c.remove(new BasicDBObject( "user" , username )); + } + + String _hash( String username , char[] passwd ){ + ByteArrayOutputStream bout = new ByteArrayOutputStream( username.length() + 20 + passwd.length ); + try { + bout.write( username.getBytes() ); + bout.write( ":mongo:".getBytes() ); + for ( int i=0; i= 128 ) + throw new IllegalArgumentException( "can't handle non-ascii passwords yet" ); + bout.write( (byte)passwd[i] ); + } + } + catch ( IOException ioe ){ + throw new RuntimeException( "impossible" , ioe ); + } + return Util.hexMD5( bout.toByteArray() ); + } + + /** + * Returns the last error that occurred since start of database or a call to resetError() + * + * The return object will look like + * + *
    +     * { err : errorMessage, nPrev : countOpsBack, ok : 1 }
    +     *  
    + * + * The value for errorMessage will be null of no error has occurred, otherwise the error message. + * The value of countOpsBack will be the number of operations since the error occurred. + * + * Care must be taken to ensure that calls to getPreviousError go to the same connection as that + * of the previous operation. + * See {@link DB#requestStart()} for more information. + * + * @return DBObject with error and status information + * @throws MongoException + */ + public CommandResult getPreviousError() + throws MongoException { + return command(new BasicDBObject("getpreverror", 1)); + } + + /** + * Resets the error memory for this database. + * Used to clear all errors such that {@link DB#getPreviousError()} will return no error. + * @throws MongoException + */ + public void resetError() + throws MongoException { + command(new BasicDBObject("reseterror", 1)); + } + + /** + * For testing purposes only - this method forces an error to help test error handling + * @throws MongoException + */ + public void forceError() + throws MongoException { + command(new BasicDBObject("forceerror", 1)); + } + + /** + * Gets the Mongo instance + * @return + */ + public Mongo getMongo(){ + return _mongo; + } + + /** + * Gets another database on same server + * @param name name of the database + * @return + */ + public DB getSisterDB( String name ){ + return _mongo.getDB( name ); + } + + /** + * Makes it possible to execute "read" queries on a slave node + * + * @deprecated Replaced with ReadPreference.SECONDARY + * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY + */ + @Deprecated + public void slaveOk(){ + addOption( Bytes.QUERYOPTION_SLAVEOK ); + } + + /** + * Adds the give option + * @param option + */ + public void addOption( int option ){ + _options.add( option ); + } + + /** + * Sets the query options + * @param options + */ + public void setOptions( int options ){ + _options.set( options ); + } + + /** + * Resets the query options + */ + public void resetOptions(){ + _options.reset(); + } + + /** + * Gets the query options + * @return + */ + public int getOptions(){ + return _options.get(); + } + + public abstract void cleanCursors( boolean force ) throws MongoException; + + + final Mongo _mongo; + final String _name; + + protected boolean _readOnly = false; + private com.massivecraft.mcore3.lib.mongodb.WriteConcern _concern; + private com.massivecraft.mcore3.lib.mongodb.ReadPreference _readPref; + final Bytes.OptionHolder _options; + + String _username; + byte[] _authhash = null; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java b/src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java new file mode 100644 index 00000000..276b0744 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java @@ -0,0 +1,186 @@ +// DBAddress.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Represents a database address + */ +public class DBAddress extends ServerAddress { + + /** Creates a new address + * Accepts as the parameter format: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    name"mydb"
    <host>/name"127.0.0.1/mydb"
    <host>:<port>/name"127.0.0.1:8080/mydb"
    + * @param urlFormat + * @throws UnknownHostException + */ + public DBAddress( String urlFormat ) + throws UnknownHostException { + super( _getHostSection( urlFormat ) ); + + _check( urlFormat , "urlFormat" ); + _db = _fixName( _getDBSection( urlFormat ) ); + + _check( _host , "host" ); + _check( _db , "db" ); + } + + static String _getHostSection( String urlFormat ){ + if ( urlFormat == null ) + throw new NullPointerException( "urlFormat can't be null" ); + int idx = urlFormat.indexOf( "/" ); + if ( idx >= 0 ) + return urlFormat.substring( 0 , idx ); + return null; + } + + static String _getDBSection( String urlFormat ){ + if ( urlFormat == null ) + throw new NullPointerException( "urlFormat can't be null" ); + int idx = urlFormat.indexOf( "/" ); + if ( idx >= 0 ) + return urlFormat.substring( idx + 1 ); + return urlFormat; + } + + static String _fixName( String name ){ + name = name.replace( '.' , '-' ); + return name; + } + + /** + * @param other an existing DBAddress that gives the host and port + * @param dbname the database to which to connect + * @throws UnknownHostException + */ + public DBAddress( DBAddress other , String dbname ) + throws UnknownHostException { + this( other._host , other._port , dbname ); + } + + /** + * @param host host name + * @param dbname database name + * @throws UnknownHostException + */ + public DBAddress( String host , String dbname ) + throws UnknownHostException { + this( host , DBPort.PORT , dbname ); + } + + /** + * @param host host name + * @param port database port + * @param dbname database name + * @throws UnknownHostException + */ + public DBAddress( String host , int port , String dbname ) + throws UnknownHostException { + super( host , port ); + _db = dbname.trim(); + } + + /** + * @param addr host address + * @param port database port + * @param dbname database name + */ + public DBAddress( InetAddress addr , int port , String dbname ){ + super( addr , port ); + _check( dbname , "name" ); + _db = dbname.trim(); + } + + static void _check( String thing , String name ){ + if ( thing == null ) + throw new NullPointerException( name + " can't be null " ); + + thing = thing.trim(); + if ( thing.length() == 0 ) + throw new IllegalArgumentException( name + " can't be empty" ); + } + + @Override + public int hashCode(){ + return super.hashCode() + _db.hashCode(); + } + + @Override + public boolean equals( Object other ){ + if ( other instanceof DBAddress ){ + DBAddress a = (DBAddress)other; + return + a._port == _port && + a._db.equals( _db ) && + a._host.equals( _host ); + } else if ( other instanceof ServerAddress ){ + return other.equals(this); + } + return false; + } + + + /** + * creates a DBAddress pointing to a different database on the same server + * @param name database name + * @return + */ + public DBAddress getSister( String name ){ + try { + return new DBAddress( _host , _port , name ); + } + catch ( UnknownHostException uh ){ + throw new MongoInternalException( "shouldn't be possible" , uh ); + } + } + + /** + * gets the database name + * @return + */ + public String getDBName(){ + return _db; + } + + /** + * gets a String representation of address as host:port/dbname. + * @return this address + */ + @Override + public String toString(){ + return super.toString() + "/" + _db; + } + + final String _db; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java b/src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java new file mode 100644 index 00000000..7da1bed5 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java @@ -0,0 +1,575 @@ +// DBApiLayer.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; +import com.massivecraft.mcore3.lib.mongodb.util.JSON; + +/** Database API + * This cannot be directly instantiated, but the functions are available + * through instances of Mongo. + */ +public class DBApiLayer extends DB { + + static final boolean D = Boolean.getBoolean( "DEBUG.DB" ); + /** The maximum number of cursors allowed */ + static final int NUM_CURSORS_BEFORE_KILL = 100; + static final int NUM_CURSORS_PER_BATCH = 20000; + + // --- show + + static final Logger TRACE_LOGGER = Logger.getLogger( "com.mongodb.TRACE" ); + static final Level TRACE_LEVEL = Boolean.getBoolean( "DB.TRACE" ) ? Level.INFO : Level.FINEST; + + static final boolean willTrace(){ + return TRACE_LOGGER.isLoggable( TRACE_LEVEL ); + } + + static final void trace( String s ){ + TRACE_LOGGER.log( TRACE_LEVEL , s ); + } + + static int chooseBatchSize(int batchSize, int limit, int fetched) { + int bs = Math.abs(batchSize); + int remaining = limit > 0 ? limit - fetched : 0; + int res = 0; + if (bs == 0 && remaining > 0) + res = remaining; + else if (bs > 0 && remaining == 0) + res = bs; + else + res = Math.min(bs, remaining); + + if (batchSize < 0) { + // force close + res = -res; + } + + if (res == 1) { + // optimization: use negative batchsize to close cursor + res = -1; + } + return res; + } + + /** + * @param mongo the Mongo instance + * @param name the database name + * @param connector the connector + */ + protected DBApiLayer( Mongo mongo, String name , DBConnector connector ){ + super( mongo, name ); + + if ( connector == null ) + throw new IllegalArgumentException( "need a connector: " + name ); + + _root = name; + _rootPlusDot = _root + "."; + + _connector = connector; + } + + public void requestStart(){ + _connector.requestStart(); + } + + public void requestDone(){ + _connector.requestDone(); + } + + public void requestEnsureConnection(){ + _connector.requestEnsureConnection(); + } + + protected MyCollection doGetCollection( String name ){ + MyCollection c = _collections.get( name ); + if ( c != null ) + return c; + + c = new MyCollection( name ); + MyCollection old = _collections.putIfAbsent(name, c); + return old != null ? old : c; + } + + String _removeRoot( String ns ){ + if ( ! ns.startsWith( _rootPlusDot ) ) + return ns; + return ns.substring( _root.length() + 1 ); + } + + public void cleanCursors( boolean force ) + throws MongoException { + + int sz = _deadCursorIds.size(); + + if ( sz == 0 || ( ! force && sz < NUM_CURSORS_BEFORE_KILL)) + return; + + Bytes.LOGGER.info( "going to kill cursors : " + sz ); + + Map> m = new HashMap>(); + DeadCursor c; + while (( c = _deadCursorIds.poll()) != null ){ + List x = m.get( c.host ); + if ( x == null ){ + x = new LinkedList(); + m.put( c.host , x ); + } + x.add( c.id ); + } + + for ( Map.Entry> e : m.entrySet() ){ + try { + killCursors( e.getKey() , e.getValue() ); + } + catch ( Throwable t ){ + Bytes.LOGGER.log( Level.WARNING , "can't clean cursors" , t ); + for ( Long x : e.getValue() ) + _deadCursorIds.add( new DeadCursor( x , e.getKey() ) ); + } + } + } + + void killCursors( ServerAddress addr , List all ) + throws MongoException { + if ( all == null || all.size() == 0 ) + return; + + OutMessage om = new OutMessage( _mongo , 2007 ); + om.writeInt( 0 ); // reserved + + om.writeInt( Math.min( NUM_CURSORS_PER_BATCH , all.size() ) ); + + int soFar = 0; + int totalSoFar = 0; + for (Long l : all) { + om.writeLong(l); + + totalSoFar++; + soFar++; + + if ( soFar >= NUM_CURSORS_PER_BATCH ){ + _connector.say( this , om ,com.massivecraft.mcore3.lib.mongodb.WriteConcern.NONE ); + om = new OutMessage( _mongo , 2007 ); + om.writeInt( 0 ); // reserved + om.writeInt( Math.min( NUM_CURSORS_PER_BATCH , all.size() - totalSoFar ) ); + soFar = 0; + } + } + + _connector.say( this , om ,com.massivecraft.mcore3.lib.mongodb.WriteConcern.NONE , addr ); + } + + class MyCollection extends DBCollection { + MyCollection( String name ){ + super( DBApiLayer.this , name ); + _fullNameSpace = _root + "." + name; + } + + public void doapply( DBObject o ){ + } + + @Override + public void drop() throws MongoException { + _collections.remove(getName()); + super.drop(); + } + + public WriteResult insert(DBObject[] arr, com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder ) + throws MongoException { + return insert( arr, true, concern, encoder ); + } + + protected WriteResult insert(DBObject[] arr, boolean shouldApply , com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder ) + throws MongoException { + + if (encoder == null) + encoder = DefaultDBEncoder.FACTORY.create(); + + if ( willTrace() ) { + for (DBObject o : arr) { + trace( "save: " + _fullNameSpace + " " + JSON.serialize( o ) ); + } + } + + if ( shouldApply ){ + for ( int i=0; i 2 * maxsize ){ + cur++; + break; + } + } + + last = _connector.say( _db , om , concern ); + } + + return last; + } + + public WriteResult remove( DBObject o , com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder ) + throws MongoException { + + if (encoder == null) + encoder = DefaultDBEncoder.FACTORY.create(); + + if ( willTrace() ) trace( "remove: " + _fullNameSpace + " " + JSON.serialize( o ) ); + + OutMessage om = new OutMessage( _mongo , 2006, encoder ); + + om.writeInt( 0 ); // reserved + om.writeCString( _fullNameSpace ); + + Collection keys = o.keySet(); + + if ( keys.size() == 1 && + keys.iterator().next().equals( "_id" ) && + o.get( keys.iterator().next() ) instanceof ObjectId ) + om.writeInt( 1 ); + else + om.writeInt( 0 ); + + om.putObject( o ); + + return _connector.say( _db , om , concern ); + } + + @Override + Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize, int limit , int options, ReadPreference readPref, DBDecoder decoder ) + throws MongoException { + + return __find(ref, fields, numToSkip, batchSize, limit, options, readPref, decoder, DefaultDBEncoder.FACTORY.create()); + } + + @Override + Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options, + ReadPreference readPref, DBDecoder decoder, DBEncoder encoder ) throws MongoException { + + if ( ref == null ) + ref = new BasicDBObject(); + + if ( willTrace() ) trace( "find: " + _fullNameSpace + " " + JSON.serialize( ref ) ); + + OutMessage query = OutMessage.query( _mongo , options , _fullNameSpace , numToSkip , chooseBatchSize(batchSize, limit, 0) , ref , fields, readPref, + encoder); + + Response res = _connector.call( _db , this , query , null , 2, readPref, decoder ); + + if ( res.size() == 1 ){ + BSONObject foo = res.get(0); + MongoException e = MongoException.parse( foo ); + if ( e != null && ! _name.equals( "$cmd" ) ) + throw e; + } + + return new Result( this , res , batchSize, limit , options, decoder ); + } + + @Override + public WriteResult update( DBObject query , DBObject o , boolean upsert , boolean multi , com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder ) + throws MongoException { + + if (encoder == null) + encoder = DefaultDBEncoder.FACTORY.create(); + + if (o != null && !o.keySet().isEmpty()) { + // if 1st key doesn't start with $, then object will be inserted as is, need to check it + String key = o.keySet().iterator().next(); + if (!key.startsWith("$")) + _checkObject(o, false, false); + } + + if ( willTrace() ) trace( "update: " + _fullNameSpace + " " + JSON.serialize( query ) + " " + JSON.serialize( o ) ); + + OutMessage om = new OutMessage( _mongo , 2001, encoder ); + om.writeInt( 0 ); // reserved + om.writeCString( _fullNameSpace ); + + int flags = 0; + if ( upsert ) flags |= 1; + if ( multi ) flags |= 2; + om.writeInt( flags ); + + om.putObject( query ); + om.putObject( o ); + + return _connector.say( _db , om , concern ); + } + + public void createIndex( final DBObject keys, final DBObject options, DBEncoder encoder ) + throws MongoException { + + if (encoder == null) + encoder = DefaultDBEncoder.FACTORY.create(); + + DBObject full = new BasicDBObject(); + for ( String k : options.keySet() ) + full.put( k , options.get( k ) ); + full.put( "key" , keys ); + + MyCollection idxs = DBApiLayer.this.doGetCollection( "system.indexes" ); + //query first, maybe we should do an update w/upsert? -- need to test performance and lock behavior + if ( idxs.findOne( full ) == null ) + idxs.insert( new DBObject[] { full }, false, WriteConcern.SAFE, encoder ); + } + + final String _fullNameSpace; + } + + class Result implements Iterator { + + Result( MyCollection coll , Response res , int batchSize, int limit , int options, DBDecoder decoder ){ + _collection = coll; + _batchSize = batchSize; + _limit = limit; + _options = options; + _host = res._host; + _decoder = decoder; + init( res ); + } + + private void init( Response res ){ + _totalBytes += res._len; + _curResult = res; + _cur = res.iterator(); + _sizes.add( res.size() ); + _numFetched += res.size(); + + if ( ( res._flags & Bytes.RESULTFLAG_CURSORNOTFOUND ) > 0 ){ + throw new MongoException.CursorNotFound(res._cursor, res.serverUsed()); + } + + if (res._cursor != 0 && _limit > 0 && _limit - _numFetched <= 0) { + // fetched all docs within limit, close cursor server-side + killCursor(); + } + } + + public DBObject next(){ + if ( _cur.hasNext() ) { + return _cur.next(); + } + + if ( ! _curResult.hasGetMore( _options ) ) + throw new RuntimeException( "no more" ); + + _advance(); + return next(); + } + + public boolean hasNext(){ + boolean hasNext = _cur.hasNext(); + while ( !hasNext ) { + if ( ! _curResult.hasGetMore( _options ) ) + return false; + + _advance(); + hasNext = _cur.hasNext(); + + if (!hasNext) { + if ( ( _options & Bytes.QUERYOPTION_AWAITDATA ) == 0 ) { + // dont block waiting for data if no await + return false; + } else { + // if await, driver should block until data is available + // if server does not support await, driver must sleep to avoid busy loop + if ((_curResult._flags & Bytes.RESULTFLAG_AWAITCAPABLE) == 0) { + try { + Thread.sleep(500); + } catch (Exception e) { + } + } + } + } + } + return hasNext; + } + + private void _advance(){ + + if ( _curResult.cursor() <= 0 ) + throw new RuntimeException( "can't advance a cursor <= 0" ); + + OutMessage m = new OutMessage( _mongo , 2005 ); + + m.writeInt( 0 ); + m.writeCString( _collection._fullNameSpace ); + m.writeInt( chooseBatchSize(_batchSize, _limit, _numFetched) ); + m.writeLong( _curResult.cursor() ); + + Response res = _connector.call( DBApiLayer.this , _collection , m , _host, _decoder ); + _numGetMores++; + init( res ); + } + + public void remove(){ + throw new RuntimeException( "can't remove this way" ); + } + + public int getBatchSize(){ + return _batchSize; + } + + public void setBatchSize(int size){ + _batchSize = size; + } + + public String toString(){ + return "DBCursor"; + } + + protected void finalize() throws Throwable { + if (_curResult != null) { + long curId = _curResult.cursor(); + _curResult = null; + _cur = null; + if (curId != 0) { + _deadCursorIds.add(new DeadCursor(curId, _host)); + } + } + super.finalize(); + } + + public long totalBytes(){ + return _totalBytes; + } + + public long getCursorId(){ + if ( _curResult == null ) + return 0; + return _curResult._cursor; + } + + int numGetMores(){ + return _numGetMores; + } + + List getSizes(){ + return Collections.unmodifiableList( _sizes ); + } + + void close(){ + // not perfectly thread safe here, may need to use an atomicBoolean + if (_curResult != null) { + killCursor(); + _curResult = null; + _cur = null; + } + } + + void killCursor() { + if (_curResult == null) + return; + long curId = _curResult.cursor(); + if (curId == 0) + return; + + List l = new ArrayList(); + l.add(curId); + + try { + killCursors(_host, l); + } catch (Throwable t) { + Bytes.LOGGER.log(Level.WARNING, "can't clean 1 cursor", t); + _deadCursorIds.add(new DeadCursor(curId, _host)); + } + _curResult._cursor = 0; + } + + public ServerAddress getServerAddress() { + return _host; + } + + Response _curResult; + Iterator _cur; + int _batchSize; + int _limit; + final DBDecoder _decoder; + final MyCollection _collection; + final int _options; + final ServerAddress _host; // host where first went. all subsequent have to go there + + private long _totalBytes = 0; + private int _numGetMores = 0; + private List _sizes = new ArrayList(); + private int _numFetched = 0; + + } // class Result + + static class DeadCursor { + + DeadCursor( long a , ServerAddress b ){ + id = a; + host = b; + } + + final long id; + final ServerAddress host; + } + + final String _root; + final String _rootPlusDot; + final DBConnector _connector; + final ConcurrentHashMap _collections = new ConcurrentHashMap(); + + ConcurrentLinkedQueue _deadCursorIds = new ConcurrentLinkedQueue(); + + static final List EMPTY = Collections.unmodifiableList( new LinkedList() ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java new file mode 100644 index 00000000..e49bbd28 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java @@ -0,0 +1,30 @@ +// DBCallback.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + + +import com.massivecraft.mcore3.lib.bson.BSONCallback; + +/** + * The DB callback interface. + */ +public interface DBCallback extends BSONCallback { + +} + diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java new file mode 100644 index 00000000..2f6d9650 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2011 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * The DBCallback factory interface. + */ +public interface DBCallbackFactory { + + public DBCallback create( DBCollection collection ); + +} + diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java new file mode 100644 index 00000000..a0d63f96 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java @@ -0,0 +1,1522 @@ +// DBCollection.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +// Mongo +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +/** This class provides a skeleton implementation of a database collection. + *

    A typical invocation sequence is thus + *

    + *     Mongo mongo = new Mongo( new DBAddress( "localhost", 127017 ) );
    + *     DB db = mongo.getDB( "mydb" );
    + *     DBCollection collection = db.getCollection( "test" );
    + * 
    + * @dochub collections + */ +@SuppressWarnings("unchecked") +public abstract class DBCollection { + + /** + * Saves document(s) to the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param arr array of documents to save + * @param concern the write concern + * @return + * @throws MongoException + * @dochub insert + */ + public WriteResult insert(DBObject[] arr , WriteConcern concern ) throws MongoException { + return insert( arr, concern, getDBEncoder()); + } + + /** + * Saves document(s) to the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param arr array of documents to save + * @param concern the write concern + * @param encoder the DBEncoder to use + * @return + * @throws MongoException + * @dochub insert + */ + public abstract WriteResult insert(DBObject[] arr , WriteConcern concern, DBEncoder encoder) throws MongoException; + + /** + * Inserts a document into the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param o + * @param concern the write concern + * @return + * @throws MongoException + * @dochub insert + */ + public WriteResult insert(DBObject o , WriteConcern concern ) + throws MongoException { + return insert( new DBObject[]{ o } , concern ); + } + + /** + * Saves document(s) to the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param arr array of documents to save + * @return + * @throws MongoException + * @dochub insert + */ + public WriteResult insert(DBObject ... arr) + throws MongoException { + return insert( arr , getWriteConcern() ); + } + + /** + * Saves document(s) to the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param arr array of documents to save + * @return + * @throws MongoException + * @dochub insert + */ + public WriteResult insert(WriteConcern concern, DBObject ... arr) + throws MongoException { + return insert( arr, concern ); + } + + /** + * Saves document(s) to the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param list list of documents to save + * @return + * @throws MongoException + * @dochub insert + */ + public WriteResult insert(List list ) + throws MongoException { + return insert( list, getWriteConcern() ); + } + + /** + * Saves document(s) to the database. + * if doc doesn't have an _id, one will be added + * you can get the _id that was added from doc after the insert + * + * @param list list of documents to save + * @param concern the write concern + * @return + * @throws MongoException + * @dochub insert + */ + public WriteResult insert(List list, WriteConcern concern ) + throws MongoException { + return insert( list.toArray( new DBObject[list.size()] ) , concern ); + } + + /** + * Performs an update operation. + * @param q search query for old object to update + * @param o object with which to update q + * @param upsert if the database should create the element if it does not exist + * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will + * not be inserted if it does not exist in the collection and upsert=true and multi=true. + * See http://www.mongodb.org/display/DOCS/Atomic+Operations + * @param concern the write concern + * @return + * @throws MongoException + * @dochub update + */ + public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern ) throws MongoException { + return update( q, o, upsert, multi, concern, getDBEncoder()); + } + + /** + * Performs an update operation. + * @param q search query for old object to update + * @param o object with which to update q + * @param upsert if the database should create the element if it does not exist + * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will + * not be inserted if it does not exist in the collection and upsert=true and multi=true. + * See http://www.mongodb.org/display/DOCS/Atomic+Operations + * @param concern the write concern + * @param encoder the DBEncoder to use + * @return + * @throws MongoException + * @dochub update + */ + public abstract WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern, DBEncoder encoder ) throws MongoException ; + + /** + * calls {@link DBCollection#update(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean, com.massivecraft.mcore3.lib.mongodb.WriteConcern)} with default WriteConcern. + * @param q search query for old object to update + * @param o object with which to update q + * @param upsert if the database should create the element if it does not exist + * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above) + * See http://www.mongodb.org/display/DOCS/Atomic+Operations + * @return + * @throws MongoException + * @dochub update + */ + public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi ) + throws MongoException { + return update( q , o , upsert , multi , getWriteConcern() ); + } + + /** + * calls {@link DBCollection#update(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=false + * @param q search query for old object to update + * @param o object with which to update q + * @return + * @throws MongoException + * @dochub update + */ + public WriteResult update( DBObject q , DBObject o ) throws MongoException { + return update( q , o , false , false ); + } + + /** + * calls {@link DBCollection#update(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=true + * @param q search query for old object to update + * @param o object with which to update q + * @return + * @throws MongoException + * @dochub update + */ + public WriteResult updateMulti( DBObject q , DBObject o ) throws MongoException { + return update( q , o , false , true ); + } + + /** + * Adds any necessary fields to a given object before saving it to the collection. + * @param o object to which to add the fields + */ + protected abstract void doapply( DBObject o ); + + /** + * Removes objects from the database collection. + * @param o the object that documents to be removed must match + * @param concern WriteConcern for this operation + * @return + * @throws MongoException + * @dochub remove + */ + public WriteResult remove( DBObject o , WriteConcern concern ) throws MongoException { + return remove( o, concern, getDBEncoder()); + } + + /** + * Removes objects from the database collection. + * @param o the object that documents to be removed must match + * @param concern WriteConcern for this operation + * @param encoder the DBEncoder to use + * @return + * @throws MongoException + * @dochub remove + */ + public abstract WriteResult remove( DBObject o , WriteConcern concern, DBEncoder encoder ) throws MongoException ; + + /** + * calls {@link DBCollection#remove(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.WriteConcern)} with the default WriteConcern + * @param o the object that documents to be removed must match + * @return + * @throws MongoException + * @dochub remove + */ + public WriteResult remove( DBObject o ) + throws MongoException { + return remove( o , getWriteConcern() ); + } + + + /** + * Finds objects + */ + abstract Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options, ReadPreference readPref, DBDecoder decoder ) throws MongoException ; + + abstract Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options, + ReadPreference readPref, DBDecoder decoder, DBEncoder encoder ) throws MongoException ; + + + /** + * Calls {@link DBCollection#find(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, int, int)} and applies the query options + * @param query query used to search + * @param fields the fields of matching objects to return + * @param numToSkip number of objects to skip + * @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) } + * @param options - see Bytes QUERYOPTION_* + * @return the cursor + * @throws MongoException + * @dochub find + */ + @Deprecated + public DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException{ + return find(query, fields, numToSkip, batchSize).addOption(options); + } + + + /** + * Finds objects from the database that match a query. + * A DBCursor object is returned, that can be iterated to go through the results. + * + * @param query query used to search + * @param fields the fields of matching objects to return + * @param numToSkip number of objects to skip + * @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) } + * @return the cursor + * @throws MongoException + * @dochub find + */ + @Deprecated + public DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize ) { + DBCursor cursor = find(query, fields).skip(numToSkip).batchSize(batchSize); + return cursor; + } + + // ------ + + /** + * Finds an object by its id. + * This compares the passed in value to the _id field of the document + * + * @param obj any valid object + * @return the object, if found, otherwise null + * @throws MongoException + */ + public DBObject findOne( Object obj ) + throws MongoException { + return findOne(obj, null); + } + + + /** + * Finds an object by its id. + * This compares the passed in value to the _id field of the document + * + * @param obj any valid object + * @param fields fields to return + * @return the object, if found, otherwise null + * @dochub find + */ + public DBObject findOne( Object obj, DBObject fields ) { + Iterator iterator = __find( new BasicDBObject("_id", obj), fields, 0, -1, 0, getOptions(), getReadPreference(), getDecoder() ); + return (iterator.hasNext() ? iterator.next() : null); + } + + /** + * Finds the first document in the query and updates it. + * @param query query to match + * @param fields fields to be returned + * @param sort sort to apply before picking first document + * @param remove if true, document found will be removed + * @param update update to apply + * @param returnNew if true, the updated document is returned, otherwise the old document is returned (or it would be lost forever) + * @param upsert do upsert (insert if document not present) + * @return the document + */ + public DBObject findAndModify(DBObject query, DBObject fields, DBObject sort, boolean remove, DBObject update, boolean returnNew, boolean upsert) { + + BasicDBObject cmd = new BasicDBObject( "findandmodify", _name); + if (query != null && !query.keySet().isEmpty()) + cmd.append( "query", query ); + if (fields != null && !fields.keySet().isEmpty()) + cmd.append( "fields", fields ); + if (sort != null && !sort.keySet().isEmpty()) + cmd.append( "sort", sort ); + + if (remove) + cmd.append( "remove", remove ); + else { + if (update != null && !update.keySet().isEmpty()) { + // if 1st key doesnt start with $, then object will be inserted as is, need to check it + String key = update.keySet().iterator().next(); + if (key.charAt(0) != '$') + _checkObject(update, false, false); + cmd.append( "update", update ); + } + if (returnNew) + cmd.append( "new", returnNew ); + if (upsert) + cmd.append( "upsert", upsert ); + } + + if (remove && !(update == null || update.keySet().isEmpty() || returnNew)) + throw new MongoException("FindAndModify: Remove cannot be mixed with the Update, or returnNew params!"); + + CommandResult res = this._db.command( cmd ); + if (res.ok() || res.getErrorMessage().equals( "No matching object found" )) { + return replaceWithObjectClass((DBObject) res.get( "value" )); + } + res.throwOnError(); + return null; + } + + /** + * Doesn't yet handle internal classes properly, so this method only does something if object class is set but + * no internal classes are set. + * + * @param oldObj the original value from the command result + * @return replaced object if necessary, or oldObj + */ + private DBObject replaceWithObjectClass(DBObject oldObj) { + if (oldObj == null || getObjectClass() == null & _internalClass.isEmpty()) { + return oldObj; + } + + DBObject newObj = instantiateObjectClassInstance(); + + for (String key : oldObj.keySet()) { + newObj.put(key, oldObj.get(key)); + } + return newObj; + } + + private DBObject instantiateObjectClassInstance() { + try { + return (DBObject) getObjectClass().newInstance(); + } catch (InstantiationException e) { + throw new MongoInternalException("can't create instance of type " + getObjectClass(), e); + } catch (IllegalAccessException e) { + throw new MongoInternalException("can't create instance of type " + getObjectClass(), e); + } + } + + + /** + * calls {@link DBCollection#findAndModify(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} + * with fields=null, remove=false, returnNew=false, upsert=false + * @param query + * @param sort + * @param update + * @return the old document + */ + public DBObject findAndModify( DBObject query , DBObject sort , DBObject update){ + return findAndModify( query, null, sort, false, update, false, false); + } + + /** + * calls {@link DBCollection#findAndModify(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} + * with fields=null, sort=null, remove=false, returnNew=false, upsert=false + * @param query + * @param update + * @return the old document + */ + public DBObject findAndModify( DBObject query , DBObject update ) { + return findAndModify( query, null, null, false, update, false, false ); + } + + /** + * calls {@link DBCollection#findAndModify(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} + * with fields=null, sort=null, remove=true, returnNew=false, upsert=false + * @param query + * @return the removed document + */ + public DBObject findAndRemove( DBObject query ) { + return findAndModify( query, null, null, true, null, false, false ); + } + + // --- START INDEX CODE --- + + /** + * calls {@link DBCollection#createIndex(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with default index options + * @param keys an object with a key set of the fields desired for the index + * @throws MongoException + */ + public void createIndex( final DBObject keys ) + throws MongoException { + createIndex( keys , defaultOptions( keys ) ); + } + + /** + * Forces creation of an index on a set of fields, if one does not already exist. + * @param keys + * @param options + * @throws MongoException + */ + public void createIndex( DBObject keys , DBObject options ) throws MongoException { + createIndex( keys, options, getDBEncoder()); + } + + /** + * Forces creation of an index on a set of fields, if one does not already exist. + * @param keys + * @param options + * @param encoder the DBEncoder to use + * @throws MongoException + */ + public abstract void createIndex( DBObject keys , DBObject options, DBEncoder encoder ) throws MongoException; + + /** + * Creates an ascending index on a field with default options, if one does not already exist. + * @param name name of field to index on + */ + public void ensureIndex( final String name ){ + ensureIndex( new BasicDBObject( name , 1 ) ); + } + + /** + * calls {@link DBCollection#ensureIndex(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with default options + * @param keys an object with a key set of the fields desired for the index + * @throws MongoException + */ + public void ensureIndex( final DBObject keys ) + throws MongoException { + ensureIndex( keys , defaultOptions( keys ) ); + } + + /** + * calls {@link DBCollection#ensureIndex(com.massivecraft.mcore3.lib.mongodb.DBObject, java.lang.String, boolean)} with unique=false + * @param keys fields to use for index + * @param name an identifier for the index + * @throws MongoException + * @dochub indexes + */ + public void ensureIndex( DBObject keys , String name ) + throws MongoException { + ensureIndex( keys , name , false ); + } + + /** + * Ensures an index on this collection (that is, the index will be created if it does not exist). + * @param keys fields to use for index + * @param name an identifier for the index. If null or empty, the default name will be used. + * @param unique if the index should be unique + * @throws MongoException + */ + public void ensureIndex( DBObject keys , String name , boolean unique ) + throws MongoException { + DBObject options = defaultOptions( keys ); + if (name != null && name.length()>0) + options.put( "name" , name ); + if ( unique ) + options.put( "unique" , Boolean.TRUE ); + ensureIndex( keys , options ); + } + + /** + * Creates an index on a set of fields, if one does not already exist. + * @param keys an object with a key set of the fields desired for the index + * @param optionsIN options for the index (name, unique, etc) + * @throws MongoException + */ + public void ensureIndex( final DBObject keys , final DBObject optionsIN ) + throws MongoException { + + if ( checkReadOnly( false ) ) return; + + final DBObject options = defaultOptions( keys ); + for ( String k : optionsIN.keySet() ) + options.put( k , optionsIN.get( k ) ); + + final String name = options.get( "name" ).toString(); + + if ( _createdIndexes.contains( name ) ) + return; + + createIndex( keys , options ); + _createdIndexes.add( name ); + } + + /** + * Clears all indices that have not yet been applied to this collection. + */ + public void resetIndexCache(){ + _createdIndexes.clear(); + } + + DBObject defaultOptions( DBObject keys ){ + DBObject o = new BasicDBObject(); + o.put( "name" , genIndexName( keys ) ); + o.put( "ns" , _fullName ); + return o; + } + + /** + * Convenience method to generate an index name from the set of fields it is over. + * @param keys the names of the fields used in this index + * @return a string representation of this index's fields + */ + public static String genIndexName( DBObject keys ){ + StringBuilder name = new StringBuilder(); + for ( String s : keys.keySet() ){ + if ( name.length() > 0 ) + name.append( '_' ); + name.append( s ).append( '_' ); + Object val = keys.get( s ); + if ( val instanceof Number || val instanceof String ) + name.append( val.toString().replace( ' ', '_' ) ); + } + return name.toString(); + } + + // --- END INDEX CODE --- + + /** + * Set hint fields for this collection (to optimize queries). + * @param lst a list of DBObjects to be used as hints + */ + public void setHintFields( List lst ){ + _hintFields = lst; + } + + /** + * Queries for an object in this collection. + * @param ref object for which to search + * @return an iterator over the results + * @dochub find + */ + public DBCursor find( DBObject ref ){ + return new DBCursor( this, ref, null, getReadPreference()); + } + + /** + * Queries for an object in this collection. + * + *

    + * An empty DBObject will match every document in the collection. + * Regardless of fields specified, the _id fields are always returned. + *

    + *

    + * An example that returns the "x" and "_id" fields for every document + * in the collection that has an "x" field: + *

    + *
    +     * BasicDBObject keys = new BasicDBObject();
    +     * keys.put("x", 1);
    +     *
    +     * DBCursor cursor = collection.find(new BasicDBObject(), keys);
    +     * 
    + * + * @param ref object for which to search + * @param keys fields to return + * @return a cursor to iterate over results + * @dochub find + */ + public DBCursor find( DBObject ref , DBObject keys ){ + return new DBCursor( this, ref, keys, getReadPreference()); + } + + + /** + * Queries for all objects in this collection. + * @return a cursor which will iterate over every object + * @dochub find + */ + public DBCursor find(){ + return new DBCursor( this, null, null, getReadPreference()); + } + + /** + * Returns a single object from this collection. + * @return the object found, or null if the collection is empty + * @throws MongoException + */ + public DBObject findOne() + throws MongoException { + return findOne( new BasicDBObject() ); + } + + /** + * Returns a single object from this collection matching the query. + * @param o the query object + * @return the object found, or null if no such object exists + * @throws MongoException + */ + public DBObject findOne( DBObject o ) + throws MongoException { + return findOne( o, null, getReadPreference()); + } + + /** + * Returns a single object from this collection matching the query. + * @param o the query object + * @param fields fields to return + * @return the object found, or null if no such object exists + * @dochub find + */ + public DBObject findOne( DBObject o, DBObject fields ) { + return findOne( o, fields, getReadPreference()); + } + /** + * Returns a single object from this collection matching the query. + * @param o the query object + * @param fields fields to return + * @return the object found, or null if no such object exists + * @dochub find + */ + public DBObject findOne( DBObject o, DBObject fields, ReadPreference readPref ) { + Iterator i = __find( o , fields , 0 , -1 , 0, getOptions(), readPref, getDecoder() ); + DBObject obj = (i.hasNext() ? i.next() : null); + if ( obj != null && ( fields != null && fields.keySet().size() > 0 ) ){ + obj.markAsPartialObject(); + } + return obj; + } + + // Only create a new decoder if there is a decoder factory explicitly set on the collection. Otherwise return null + // so that DBPort will use a cached decoder from the default factory. + private DBDecoder getDecoder() { + return getDBDecoderFactory() != null ? getDBDecoderFactory().create() : null; + } + + // Only create a new encoder if there is an encoder factory explicitly set on the collection. Otherwise return null + // to allow DB to create its own or use a cached one. + private DBEncoder getDBEncoder() { + return getDBEncoderFactory() != null ? getDBEncoderFactory().create() : null; + } + + + /** + * calls {@link DBCollection#apply(com.massivecraft.mcore3.lib.mongodb.DBObject, boolean)} with ensureID=true + * @param o DBObject to which to add fields + * @return the modified parameter object + */ + public Object apply( DBObject o ){ + return apply( o , true ); + } + + /** + * calls {@link DBCollection#doapply(com.massivecraft.mcore3.lib.mongodb.DBObject)}, optionally adding an automatic _id field + * @param jo object to add fields to + * @param ensureID whether to add an _id field + * @return the modified object o + */ + public Object apply( DBObject jo , boolean ensureID ){ + + Object id = jo.get( "_id" ); + if ( ensureID && id == null ){ + id = ObjectId.get(); + jo.put( "_id" , id ); + } + + doapply( jo ); + + return id; + } + + /** + * calls {@link DBCollection#save(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.WriteConcern)} with default WriteConcern + * @param jo the DBObject to save + * will add _id field to jo if needed + * @return + */ + public WriteResult save( DBObject jo ) { + return save(jo, getWriteConcern()); + } + + /** + * Saves an object to this collection (does insert or update based on the object _id). + * @param jo the DBObject to save + * @param concern the write concern + * @return + * @throws MongoException + */ + public WriteResult save( DBObject jo, WriteConcern concern ) + throws MongoException { + if ( checkReadOnly( true ) ) + return null; + + _checkObject( jo , false , false ); + + Object id = jo.get( "_id" ); + + if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){ + if ( id != null && id instanceof ObjectId ) + ((ObjectId)id).notNew(); + if ( concern == null ) + return insert( jo ); + else + return insert( jo, concern ); + } + + DBObject q = new BasicDBObject(); + q.put( "_id" , id ); + if ( concern == null ) + return update( q , jo , true , false ); + else + return update( q , jo , true , false , concern ); + + } + + // ---- DB COMMANDS ---- + /** + * Drops all indices from this collection + * @throws MongoException + */ + public void dropIndexes() + throws MongoException { + dropIndexes( "*" ); + } + + + /** + * Drops an index from this collection + * @param name the index name + * @throws MongoException + */ + public void dropIndexes( String name ) + throws MongoException { + DBObject cmd = BasicDBObjectBuilder.start() + .add( "deleteIndexes" , getName() ) + .add( "index" , name ) + .get(); + + resetIndexCache(); + CommandResult res = _db.command( cmd ); + if (res.ok() || res.getErrorMessage().equals( "ns not found" )) + return; + res.throwOnError(); + } + + /** + * Drops (deletes) this collection. Use with care. + * @throws MongoException + */ + public void drop() + throws MongoException { + resetIndexCache(); + CommandResult res =_db.command( BasicDBObjectBuilder.start().add( "drop" , getName() ).get() ); + if (res.ok() || res.getErrorMessage().equals( "ns not found" )) + return; + res.throwOnError(); + } + + /** + * returns the number of documents in this collection. + * @return + * @throws MongoException + */ + public long count() + throws MongoException { + return getCount(new BasicDBObject(), null); + } + + /** + * returns the number of documents that match a query. + * @param query query to match + * @return + * @throws MongoException + */ + public long count(DBObject query) + throws MongoException { + return getCount(query, null); + } + + + /** + * calls {@link DBCollection#getCount(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with an empty query and null fields. + * @return number of documents that match query + * @throws MongoException + */ + public long getCount() + throws MongoException { + return getCount(new BasicDBObject(), null); + } + + /** + * calls {@link DBCollection#getCount(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with null fields. + * @param query query to match + * @return + * @throws MongoException + */ + public long getCount(DBObject query) + throws MongoException { + return getCount(query, null); + } + + /** + * calls {@link DBCollection#getCount(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, long, long)} with limit=0 and skip=0 + * @param query query to match + * @param fields fields to return + * @return + * @throws MongoException + */ + public long getCount(DBObject query, DBObject fields) + throws MongoException { + return getCount( query , fields , 0 , 0 ); + } + + /** + * Returns the number of documents in the collection + * that match the specified query + * + * @param query query to select documents to count + * @param fields fields to return + * @param limit limit the count to this value + * @param skip number of entries to skip + * @return number of documents that match query and fields + * @throws MongoException + */ + public long getCount(DBObject query, DBObject fields, long limit, long skip ) + throws MongoException { + + BasicDBObject cmd = new BasicDBObject(); + cmd.put("count", getName()); + cmd.put("query", query); + if (fields != null) { + cmd.put("fields", fields); + } + + if ( limit > 0 ) + cmd.put( "limit" , limit ); + if ( skip > 0 ) + cmd.put( "skip" , skip ); + + CommandResult res = _db.command(cmd,getOptions()); + + if ( ! res.ok() ){ + String errmsg = res.getErrorMessage(); + + if ( errmsg.equals("ns does not exist") || + errmsg.equals("ns missing" ) ){ + // for now, return 0 - lets pretend it does exist + return 0; + } + + res.throwOnError(); + } + + return res.getLong("n"); + } + + /** + * Calls {@link DBCollection#rename(java.lang.String, boolean)} with dropTarget=false + * @param newName new collection name (not a full namespace) + * @return the new collection + * @throws MongoException + */ + public DBCollection rename( String newName ) + throws MongoException { + return rename(newName, false); + } + + /** + * renames of this collection to newName + * @param newName new collection name (not a full namespace) + * @param dropTarget if a collection with the new name exists, whether or not to drop it + * @return the new collection + * @throws MongoException + */ + public DBCollection rename( String newName, boolean dropTarget ) + throws MongoException { + CommandResult ret = + _db.getSisterDB( "admin" ) + .command( BasicDBObjectBuilder.start() + .add( "renameCollection" , _fullName ) + .add( "to" , _db._name + "." + newName ) + .add( "dropTarget" , dropTarget ) + .get() ); + ret.throwOnError(); + resetIndexCache(); + return _db.getCollection( newName ); + } + + /** + * calls {@link DBCollection#group(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, java.lang.String, java.lang.String)} with finalize=null + * @param key - { a : true } + * @param cond - optional condition on query + * @param reduce javascript reduce function + * @param initial initial value for first match on a key + * @return + * @throws MongoException + * @see http://www.mongodb.org/display/DOCS/Aggregation + */ + public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce ) + throws MongoException { + return group( key , cond , initial , reduce , null ); + } + + /** + * Applies a group operation + * @param key - { a : true } + * @param cond - optional condition on query + * @param reduce javascript reduce function + * @param initial initial value for first match on a key + * @param finalize An optional function that can operate on the result(s) of the reduce function. + * @return + * @throws MongoException + * @see http://www.mongodb.org/display/DOCS/Aggregation + */ + public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce , String finalize ) + throws MongoException { + GroupCommand cmd = new GroupCommand(this, key, cond, initial, reduce, finalize); + return group( cmd ); + } + + /** + * Applies a group operation + * @param cmd the group command + * @return + * @throws MongoException + * @see http://www.mongodb.org/display/DOCS/Aggregation + */ + public DBObject group( GroupCommand cmd ) { + CommandResult res = _db.command( cmd.toDBObject(), getOptions() ); + res.throwOnError(); + return (DBObject)res.get( "retval" ); + } + + + /** + * @deprecated prefer the {@link DBCollection#group(com.massivecraft.mcore3.lib.mongodb.GroupCommand)} which is more standard + * Applies a group operation + * @param args object representing the arguments to the group function + * @return + * @throws MongoException + * @see http://www.mongodb.org/display/DOCS/Aggregation + */ + @Deprecated + public DBObject group( DBObject args ) + throws MongoException { + args.put( "ns" , getName() ); + CommandResult res = _db.command( new BasicDBObject( "group" , args ), getOptions() ); + res.throwOnError(); + return (DBObject)res.get( "retval" ); + } + + /** + * find distinct values for a key + * @param key + * @return + */ + @SuppressWarnings("rawtypes") + public List distinct( String key ){ + return distinct( key , new BasicDBObject() ); + } + + /** + * find distinct values for a key + * @param key + * @param query query to match + * @return + */ + @SuppressWarnings("rawtypes") + public List distinct( String key , DBObject query ){ + DBObject c = BasicDBObjectBuilder.start() + .add( "distinct" , getName() ) + .add( "key" , key ) + .add( "query" , query ) + .get(); + + CommandResult res = _db.command( c, getOptions() ); + res.throwOnError(); + return (List)(res.get( "values" )); + } + + /** + * performs a map reduce operation + * Runs the command in REPLACE output mode (saves to named collection) + * + * @param map + * map function in javascript code + * @param outputTarget + * optional - leave null if want to use temp collection + * @param reduce + * reduce function in javascript code + * @param query + * to match + * @return + * @throws MongoException + * @dochub mapreduce + */ + public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , DBObject query ) throws MongoException{ + return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , MapReduceCommand.OutputType.REPLACE, query ) ); + } + + /** + * performs a map reduce operation + * Specify an outputType to control job execution + * * INLINE - Return results inline + * * REPLACE - Replace the output collection with the job output + * * MERGE - Merge the job output with the existing contents of outputTarget + * * REDUCE - Reduce the job output with the existing contents of + * outputTarget + * + * @param map + * map function in javascript code + * @param outputTarget + * optional - leave null if want to use temp collection + * @param outputType + * set the type of job output + * @param reduce + * reduce function in javascript code + * @param query + * to match + * @return + * @throws MongoException + * @dochub mapreduce + */ + public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , MapReduceCommand.OutputType outputType , DBObject query ) + throws MongoException{ + return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , outputType , query ) ); + } + + /** + * performs a map reduce operation + * + * @param command + * object representing the parameters + * @return + * @throws MongoException + */ + public MapReduceOutput mapReduce( MapReduceCommand command ) throws MongoException{ + DBObject cmd = command.toDBObject(); + // if type in inline, then query options like slaveOk is fine + CommandResult res = null; + if (command.getOutputType() == MapReduceCommand.OutputType.INLINE) + res = _db.command( cmd, getOptions(), command.getReadPreference() != null ? command.getReadPreference() : getReadPreference() ); + else + res = _db.command( cmd ); + res.throwOnError(); + return new MapReduceOutput( this , cmd, res ); + } + + /** + * performs a map reduce operation + * + * @param command + * object representing the parameters + * @return + * @throws MongoException + */ + public MapReduceOutput mapReduce( DBObject command ) throws MongoException{ + if ( command.get( "mapreduce" ) == null && command.get( "mapReduce" ) == null ) + throw new IllegalArgumentException( "need mapreduce arg" ); + CommandResult res = _db.command( command ); + res.throwOnError(); + return new MapReduceOutput( this , command, res ); + } + + /** + * Return a list of the indexes for this collection. Each object + * in the list is the "info document" from MongoDB + * + * @return list of index documents + */ + public List getIndexInfo() { + BasicDBObject cmd = new BasicDBObject(); + cmd.put("ns", getFullName()); + + DBCursor cur = _db.getCollection("system.indexes").find(cmd); + + List list = new ArrayList(); + + while(cur.hasNext()) { + list.add(cur.next()); + } + + return list; + } + + /** + * Drops an index from this collection + * @param keys keys of the index + * @throws MongoException + */ + public void dropIndex( DBObject keys ) + throws MongoException { + dropIndexes( genIndexName( keys ) ); + } + + /** + * Drops an index from this collection + * @param name name of index to drop + * @throws MongoException + */ + public void dropIndex( String name ) + throws MongoException { + dropIndexes( name ); + } + + /** + * gets the collections statistics ("collstats" command) + * @return + */ + public CommandResult getStats() { + return getDB().command(new BasicDBObject("collstats", getName()), getOptions()); + } + + /** + * returns whether or not this is a capped collection + * @return + */ + public boolean isCapped() { + CommandResult stats = getStats(); + Object capped = stats.get("capped"); + return(capped != null && (Integer)capped == 1); + } + + // ------ + + /** + * Initializes a new collection. No operation is actually performed on the database. + * @param base database in which to create the collection + * @param name the name of the collection + */ + protected DBCollection( DB base , String name ){ + _db = base; + _name = name; + _fullName = _db.getName() + "." + name; + _options = new Bytes.OptionHolder( _db._options ); + } + + protected DBObject _checkObject( DBObject o , boolean canBeNull , boolean query ){ + if ( o == null ){ + if ( canBeNull ) + return null; + throw new IllegalArgumentException( "can't be null" ); + } + + if ( o.isPartialObject() && ! query ) + throw new IllegalArgumentException( "can't save partial objects" ); + + if ( ! query ){ + _checkKeys(o); + } + return o; + } + + /** + * Checks key strings for invalid characters. + */ + private void _checkKeys( DBObject o ) { + for ( String s : o.keySet() ){ + validateKey ( s ); + Object inner = o.get( s ); + if ( inner instanceof DBObject ) { + _checkKeys( (DBObject)inner ); + } else if ( inner instanceof Map ) { + _checkKeys( (Map)inner ); + } + } + } + + /** + * Checks key strings for invalid characters. + */ + private void _checkKeys( Map o ) { + for ( String s : o.keySet() ){ + validateKey ( s ); + Object inner = o.get( s ); + if ( inner instanceof DBObject ) { + _checkKeys( (DBObject)inner ); + } else if ( inner instanceof Map ) { + _checkKeys( (Map)inner ); + } + } + } + + /** + * Check for invalid key names + * @param s the string field/key to check + * @exception IllegalArgumentException if the key is not valid. + */ + private void validateKey(String s ) { + if ( s.contains( "." ) ) + throw new IllegalArgumentException( "fields stored in the db can't have . in them. (Bad Key: '" + s + "')" ); + if ( s.startsWith( "$" ) ) + throw new IllegalArgumentException( "fields stored in the db can't start with '$' (Bad Key: '" + s + "')" ); + } + + /** + * Finds a collection that is prefixed with this collection's name. + * A typical use of this might be + *
    +     *    DBCollection users = mongo.getCollection( "wiki" ).getCollection( "users" );
    +     * 
    + * Which is equivalent to + *
    + * DBCollection users = mongo.getCollection( "wiki.users" ); + *
    + * @param n the name of the collection to find + * @return the matching collection + */ + public DBCollection getCollection( String n ){ + return _db.getCollection( _name + "." + n ); + } + + /** + * Returns the name of this collection. + * @return the name of this collection + */ + public String getName(){ + return _name; + } + + /** + * Returns the full name of this collection, with the database name as a prefix. + * @return the name of this collection + */ + public String getFullName(){ + return _fullName; + } + + /** + * Returns the database this collection is a member of. + * @return this collection's database + */ + public DB getDB(){ + return _db; + } + + /** + * Returns if this collection's database is read-only + * @param strict if an exception should be thrown if the database is read-only + * @return if this collection's database is read-only + * @throws RuntimeException if the database is read-only and strict is set + */ + protected boolean checkReadOnly( boolean strict ){ + if ( ! _db._readOnly ) + return false; + + if ( ! strict ) + return true; + + throw new IllegalStateException( "db is read only" ); + } + + @Override + public int hashCode(){ + return _fullName.hashCode(); + } + + @Override + public boolean equals( Object o ){ + return o == this; + } + + @Override + public String toString(){ + return _name; + } + + /** + * Sets a default class for objects in this collection; null resets the class to nothing. + * @param c the class + * @throws IllegalArgumentException if c is not a DBObject + */ + @SuppressWarnings("rawtypes") + public void setObjectClass( Class c ){ + if ( c == null ){ + // reset + _wrapper = null; + _objectClass = null; + return; + } + + if ( ! DBObject.class.isAssignableFrom( c ) ) + throw new IllegalArgumentException( c.getName() + " is not a DBObject" ); + _objectClass = c; + if ( ReflectionDBObject.class.isAssignableFrom( c ) ) + _wrapper = ReflectionDBObject.getWrapper( c ); + else + _wrapper = null; + } + + /** + * Gets the default class for objects in the collection + * @return the class + */ + @SuppressWarnings("rawtypes") + public Class getObjectClass(){ + return _objectClass; + } + + /** + * sets the internal class + * @param path + * @param c + */ + @SuppressWarnings("rawtypes") + public void setInternalClass( String path , Class c ){ + _internalClass.put( path , c ); + } + + /** + * gets the internal class + * @param path + * @return + */ + @SuppressWarnings("rawtypes") + protected Class getInternalClass( String path ){ + Class c = _internalClass.get( path ); + if ( c != null ) + return c; + + if ( _wrapper == null ) + return null; + return _wrapper.getInternalClass( path ); + } + + /** + * Set the write concern for this collection. Will be used for + * writes to this collection. Overrides any setting of write + * concern at the DB level. See the documentation for + * {@link WriteConcern} for more information. + * + * @param concern write concern to use + */ + public void setWriteConcern( WriteConcern concern ){ + _concern = concern; + } + + /** + * Get the write concern for this collection. + * @return + */ + public WriteConcern getWriteConcern(){ + if ( _concern != null ) + return _concern; + return _db.getWriteConcern(); + } + + /** + * Sets the read preference for this collection. Will be used as default + * for reads from this collection; overrides DB & Connection level settings. + * See the * documentation for {@link ReadPreference} for more information. + * + * @param preference Read Preference to use + */ + public void setReadPreference( ReadPreference preference ){ + _readPref = preference; + } + + /** + * Gets the read preference + * @return + */ + public ReadPreference getReadPreference(){ + if ( _readPref != null ) + return _readPref; + return _db.getReadPreference(); + } + /** + * makes this query ok to run on a slave node + * + * @deprecated Replaced with ReadPreference.SECONDARY + * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY + */ + @Deprecated + public void slaveOk(){ + addOption( Bytes.QUERYOPTION_SLAVEOK ); + } + + /** + * adds a default query option + * @param option + */ + public void addOption( int option ){ + _options.add(option); + } + + /** + * sets the default query options + * @param options + */ + public void setOptions( int options ){ + _options.set(options); + } + + /** + * resets the default query options + */ + public void resetOptions(){ + _options.reset(); + } + + /** + * gets the default query options + * @return + */ + public int getOptions(){ + return _options.get(); + } + + /** + * Set a customer decoder factory for this collection. Set to null to use the default from MongoOptions. + * @param fact the factory to set. + */ + public synchronized void setDBDecoderFactory(DBDecoderFactory fact) { + _decoderFactory = fact; + } + + /** + * Get the decoder factory for this collection. A null return value means that the default from MongoOptions + * is being used. + * @return the factory + */ + public synchronized DBDecoderFactory getDBDecoderFactory() { + return _decoderFactory; + } + + /** + * Set a customer encoder factory for this collection. Set to null to use the default from MongoOptions. + * @param fact the factory to set. + */ + public synchronized void setDBEncoderFactory(DBEncoderFactory fact) { + _encoderFactory = fact; + } + + /** + * Get the encoder factory for this collection. A null return value means that the default from MongoOptions + * is being used. + * @return the factory + */ + public synchronized DBEncoderFactory getDBEncoderFactory() { + return _encoderFactory; + } + + final DB _db; + + final protected String _name; + final protected String _fullName; + + protected List _hintFields; + private WriteConcern _concern = null; + private ReadPreference _readPref = null; + private DBDecoderFactory _decoderFactory; + private DBEncoderFactory _encoderFactory; + final Bytes.OptionHolder _options; + + @SuppressWarnings("rawtypes") + protected Class _objectClass = null; + @SuppressWarnings("rawtypes") + private Map _internalClass = Collections.synchronizedMap( new HashMap() ); + private ReflectionDBObject.JavaWrapper _wrapper = null; + + final private Set _createdIndexes = new HashSet(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java b/src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java new file mode 100644 index 00000000..88f08df4 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java @@ -0,0 +1,106 @@ +// DBConnector.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + + +/** + * Interface that provides the ability to exchange request/response with the database + */ +public interface DBConnector { + + /** + * initiates a "consistent request" on the thread. + * Once this has been called, the connector will ensure that the same underlying connection is always used for a given thread. + * This happens until requestStop() is called. + */ + public void requestStart(); + /** + * terminates the "consistent request". + */ + public void requestDone(); + /** + * Ensures that a connection exists for the "consistent request" + */ + public void requestEnsureConnection(); + + /** + * does a write operation + * @param db the database + * @param m the request message + * @param concern the write concern + * @return the write result + * @throws MongoException + */ + public WriteResult say( DB db , OutMessage m , WriteConcern concern ) throws MongoException; + /** + * does a write operation + * @param db the database + * @param m the request message + * @param concern the write concern + * @param hostNeeded specific server to connect to + * @return the write result + * @throws MongoException + */ + public WriteResult say( DB db , OutMessage m , WriteConcern concern , ServerAddress hostNeeded ) throws MongoException; + + /** + * does a read operation on the database + * @param db the database + * @param coll the collection + * @param m the request message + * @param hostNeeded specific server to connect to + * @param decoder the decoder to use + * @return the read result + * @throws MongoException + */ + public Response call( DB db , DBCollection coll , OutMessage m , + ServerAddress hostNeeded , DBDecoder decoder ) throws MongoException; + /** + * + * does a read operation on the database + * @param db the database + * @param coll the collection + * @param m the request message + * @param hostNeeded specific server to connect to + * @param retries the number of retries in case of an error + * @return the read result + * @throws MongoException + */ + public Response call( DB db , DBCollection coll , OutMessage m , ServerAddress hostNeeded , int retries ) throws MongoException; + + /** + * does a read operation on the database + * @param db the database + * @param coll the collection + * @param m the request message + * @param hostNeeded specific server to connect to + * @param retries number of retries in case of error + * @param readPref the read preferences + * @param decoder the decoder to use + * @return the read result + * @throws MongoException + */ + public Response call( DB db , DBCollection coll , OutMessage m , ServerAddress hostNeeded , int retries , ReadPreference readPref , DBDecoder decoder ) throws MongoException; + + /** + * returns true if the connector is in a usable state + * @return + */ + public boolean isOpen(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java new file mode 100644 index 00000000..71f0c7e9 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java @@ -0,0 +1,761 @@ +// DBCursor.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.Closeable; +import java.util.*; + +import com.massivecraft.mcore3.lib.mongodb.DBApiLayer.Result; + + +/** An iterator over database results. + * Doing a find() query on a collection returns a + * DBCursor thus + * + *
    + * DBCursor cursor = collection.find( query );
    + * if( cursor.hasNext() )
    + *     DBObject obj = cursor.next();
    + * 
    + * + *

    Warning: Calling toArray or length on + * a DBCursor will irrevocably turn it into an array. This + * means that, if the cursor was iterating over ten million results + * (which it was lazily fetching from the database), suddenly there will + * be a ten-million element array in memory. Before converting to an array, + * make sure that there are a reasonable number of results using + * skip() and limit(). + *

    For example, to get an array of the 1000-1100th elements of a cursor, use + * + *

    + * List obj = collection.find( query ).skip( 1000 ).limit( 100 ).toArray();
    + * 
    + * + * @dochub cursors + */ +public class DBCursor implements Iterator , Iterable, Closeable { + + /** + * Initializes a new database cursor + * @param collection collection to use + * @param q query to perform + * @param k keys to return from the query + * @param preference the Read Preference for this query + */ + public DBCursor( DBCollection collection , DBObject q , DBObject k, ReadPreference preference ){ + if (collection == null) { + throw new IllegalArgumentException("collection is null"); + } + _collection = collection; + _query = q == null ? new BasicDBObject() : q; + _keysWanted = k; + _options = _collection.getOptions(); + _readPref = preference; + _decoderFact = collection.getDBDecoderFactory(); + } + + /** + * Types of cursors: iterator or array. + */ + static enum CursorType { ITERATOR , ARRAY }; + + /** + * Creates a copy of an existing database cursor. + * The new cursor is an iterator, even if the original + * was an array. + * + * @return the new cursor + */ + public DBCursor copy() { + DBCursor c = new DBCursor(_collection, _query, _keysWanted, _readPref); + c._orderBy = _orderBy; + c._hint = _hint; + c._hintDBObj = _hintDBObj; + c._limit = _limit; + c._skip = _skip; + c._options = _options; + c._batchSize = _batchSize; + c._snapshot = _snapshot; + c._explain = _explain; + if ( _specialFields != null ) + c._specialFields = new BasicDBObject( _specialFields.toMap() ); + return c; + } + + /** + * creates a copy of this cursor object that can be iterated. + * Note: + * - you can iterate the DBCursor itself without calling this method + * - no actual data is getting copied. + * + * @return + */ + public Iterator iterator(){ + return this.copy(); + } + + // ---- querty modifiers -------- + + /** + * Sorts this cursor's elements. + * This method must be called before getting any object from the cursor. + * @param orderBy the fields by which to sort + * @return a cursor pointing to the first element of the sorted results + */ + public DBCursor sort( DBObject orderBy ){ + if ( _it != null ) + throw new IllegalStateException( "can't sort after executing query" ); + + _orderBy = orderBy; + return this; + } + + /** + * adds a special operator like $maxScan or $returnKey + * e.g. addSpecial( "$returnKey" , 1 ) + * e.g. addSpecial( "$maxScan" , 100 ) + * @param name + * @param o + * @return + * @dochub specialOperators + */ + public DBCursor addSpecial( String name , Object o ){ + if ( _specialFields == null ) + _specialFields = new BasicDBObject(); + _specialFields.put( name , o ); + return this; + } + + /** + * Informs the database of indexed fields of the collection in order to improve performance. + * @param indexKeys a DBObject with fields and direction + * @return same DBCursor for chaining operations + */ + public DBCursor hint( DBObject indexKeys ){ + if ( _it != null ) + throw new IllegalStateException( "can't hint after executing query" ); + + _hintDBObj = indexKeys; + return this; + } + + /** + * Informs the database of an indexed field of the collection in order to improve performance. + * @param indexName the name of an index + * @return same DBCursort for chaining operations + */ + public DBCursor hint( String indexName ){ + if ( _it != null ) + throw new IllegalStateException( "can't hint after executing query" ); + + _hint = indexName; + return this; + } + + /** + * Use snapshot mode for the query. Snapshot mode assures no duplicates are + * returned, or objects missed, which were present at both the start and end + * of the query's execution (if an object is new during the query, or deleted + * during the query, it may or may not be returned, even with snapshot mode). + * Note that short query responses (less than 1MB) are always effectively snapshotted. + * Currently, snapshot mode may not be used with sorting or explicit hints. + * @return same DBCursor for chaining operations + */ + public DBCursor snapshot() { + if (_it != null) + throw new IllegalStateException("can't snapshot after executing the query"); + + _snapshot = true; + + return this; + } + + /** + * Returns an object containing basic information about the + * execution of the query that created this cursor + * This creates a DBObject with the key/value pairs: + * "cursor" : cursor type + * "nScanned" : number of records examined by the database for this query + * "n" : the number of records that the database returned + * "millis" : how long it took the database to execute the query + * @return a DBObject + * @dochub explain + */ + public DBObject explain(){ + DBCursor c = copy(); + c._explain = true; + if (c._limit > 0) { + // need to pass a negative batchSize as limit for explain + c._batchSize = c._limit * -1; + c._limit = 0; + } + return c.next(); + } + + /** + * Limits the number of elements returned. + * Note: parameter n should be positive, although a negative value is supported for legacy reason. + * Passing a negative value will call {@link DBCursor#batchSize(int)} which is the preferred method. + * @param n the number of elements to return + * @return a cursor to iterate the results + * @dochub limit + */ + public DBCursor limit( int n ){ + if ( _it != null ) + throw new IllegalStateException( "can't set limit after executing query" ); + + if (n > 0) + _limit = n; + else if (n < 0) + batchSize(n); + return this; + } + + /** + * Limits the number of elements returned in one batch. + * A cursor typically fetches a batch of result objects and store them locally. + * + * If batchSize is positive, it represents the size of each batch of objects retrieved. + * It can be adjusted to optimize performance and limit data transfer. + * + * If batchSize is negative, it will limit of number objects returned, that fit within the max batch size limit (usually 4MB), and cursor will be closed. + * For example if batchSize is -10, then the server will return a maximum of 10 documents and as many as can fit in 4MB, then close the cursor. + * Note that this feature is different from limit() in that documents must fit within a maximum size, and it removes the need to send a request to close the cursor server-side. + * + * The batch size can be changed even after a cursor is iterated, in which case the setting will apply on the next batch retrieval. + * + * @param n the number of elements to return in a batch + * @return + */ + public DBCursor batchSize( int n ){ + // check for special case, used to have server bug with 1 + if ( n == 1 ) + n = 2; + + if ( _it != null ) { + if (_it instanceof DBApiLayer.Result) + ((DBApiLayer.Result)_it).setBatchSize(n); + } + + _batchSize = n; + return this; + } + + /** + * Discards a given number of elements at the beginning of the cursor. + * @param n the number of elements to skip + * @return a cursor pointing to the new first element of the results + * @throws RuntimeException if the cursor has started to be iterated through + */ + public DBCursor skip( int n ){ + if ( _it != null ) + throw new IllegalStateException( "can't set skip after executing query" ); + _skip = n; + return this; + } + + /** + * gets the cursor id. + * @return the cursor id, or 0 if there is no active cursor. + */ + public long getCursorId() { + if ( _it instanceof Result ) + return ((Result)_it).getCursorId(); + + return 0; + } + + /** + * kills the current cursor on the server. + */ + public void close() { + if ( _it instanceof Result ) + ((Result)_it).close(); + } + + /** + * makes this query ok to run on a slave node + * + * @return a copy of the same cursor (for chaining) + * + * @deprecated Replaced with ReadPreference.SECONDARY + * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY + */ + @Deprecated + public DBCursor slaveOk(){ + return addOption( Bytes.QUERYOPTION_SLAVEOK ); + } + + /** + * adds a query option - see Bytes.QUERYOPTION_* for list + * @param option + * @return + */ + public DBCursor addOption( int option ){ + if ( option == Bytes.QUERYOPTION_EXHAUST ) + throw new IllegalArgumentException("The exhaust option is not user settable."); + + _options |= option; + return this; + } + + /** + * sets the query option - see Bytes.QUERYOPTION_* for list + * @param options + */ + public DBCursor setOptions( int options ){ + _options = options; + return this; + } + + /** + * resets the query options + */ + public DBCursor resetOptions(){ + _options = 0; + return this; + } + + /** + * gets the query options + * @return + */ + public int getOptions(){ + return _options; + } + + // ---- internal stuff ------ + + private void _check() + throws MongoException { + if ( _it != null ) + return; + + _lookForHints(); + + DBObject foo = _query; + if (hasSpecialQueryFields()) { + foo = _specialFields == null ? new BasicDBObject() : _specialFields; + + _addToQueryObject(foo, "query", _query, true); + _addToQueryObject(foo, "orderby", _orderBy, false); + if (_hint != null) + _addToQueryObject(foo, "$hint", _hint); + if (_hintDBObj != null) + _addToQueryObject(foo, "$hint", _hintDBObj); + + if (_explain) + foo.put("$explain", true); + if (_snapshot) + foo.put("$snapshot", true); + } + + _it = _collection.__find(foo, _keysWanted, _skip, _batchSize, _limit, _options, _readPref, getDecoder()); + } + + // Only create a new decoder if there is a decoder factory explicitly set on the collection. Otherwise return null + // so that the collection can use a cached decoder + private DBDecoder getDecoder() { + return _decoderFact != null ? _decoderFact.create() : null; + } + + /** + * if there is a hint to use, use it + */ + private void _lookForHints(){ + + if ( _hint != null ) // if someone set a hint, then don't do this + return; + + if ( _collection._hintFields == null ) + return; + + Set mykeys = _query.keySet(); + + for ( DBObject o : _collection._hintFields ){ + + Set hintKeys = o.keySet(); + + if ( ! mykeys.containsAll( hintKeys ) ) + continue; + + hint( o ); + return; + } + } + + boolean hasSpecialQueryFields(){ + if ( _specialFields != null ) + return true; + + if ( _orderBy != null && _orderBy.keySet().size() > 0 ) + return true; + + if ( _hint != null || _hintDBObj != null || _snapshot ) + return true; + + return _explain; + } + + void _addToQueryObject( DBObject query , String field , DBObject thing , boolean sendEmpty ){ + if ( thing == null ) + return; + + if ( ! sendEmpty && thing.keySet().size() == 0 ) + return; + + _addToQueryObject( query , field , thing ); + } + + void _addToQueryObject( DBObject query , String field , Object thing ){ + + if ( thing == null ) + return; + + query.put( field , thing ); + } + + void _checkType( CursorType type ){ + if ( _cursorType == null ){ + _cursorType = type; + return; + } + + if ( type == _cursorType ) + return; + + throw new IllegalArgumentException( "can't switch cursor access methods" ); + } + + private DBObject _next() + throws MongoException { + if ( _cursorType == null ) + _checkType( CursorType.ITERATOR ); + + _check(); + + _cur = _it.next(); + _num++; + + if ( _keysWanted != null && _keysWanted.keySet().size() > 0 ){ + _cur.markAsPartialObject(); + //throw new UnsupportedOperationException( "need to figure out partial" ); + } + + if ( _cursorType == CursorType.ARRAY ){ + _all.add( _cur ); + } + + return _cur; + } + + /** + * gets the number of times, so far, that the cursor retrieved a batch from the database + * @return + */ + public int numGetMores(){ + if ( _it instanceof DBApiLayer.Result ) + return ((DBApiLayer.Result)_it).numGetMores(); + + throw new IllegalArgumentException("_it not a real result" ); + } + + /** + * gets a list containing the number of items received in each batch + * @return + */ + public List getSizes(){ + if ( _it instanceof DBApiLayer.Result ) + return ((DBApiLayer.Result)_it).getSizes(); + + throw new IllegalArgumentException("_it not a real result" ); + } + + private boolean _hasNext() + throws MongoException { + _check(); + + if ( _limit > 0 && _num >= _limit ) + return false; + + return _it.hasNext(); + } + + /** + * Returns the number of objects through which the cursor has iterated. + * @return the number of objects seen + */ + public int numSeen(){ + return _num; + } + + // ----- iterator api ----- + + /** + * Checks if there is another object available + * @return + * @throws MongoException + */ + public boolean hasNext() throws MongoException { + _checkType( CursorType.ITERATOR ); + return _hasNext(); + } + + /** + * Returns the object the cursor is at and moves the cursor ahead by one. + * @return the next element + * @throws MongoException + */ + public DBObject next() throws MongoException { + _checkType( CursorType.ITERATOR ); + return _next(); + } + + /** + * Returns the element the cursor is at. + * @return the next element + */ + public DBObject curr(){ + _checkType( CursorType.ITERATOR ); + return _cur; + } + + /** + * Not implemented. + */ + public void remove(){ + throw new UnsupportedOperationException( "can't remove from a cursor" ); + } + + + // ---- array api ----- + + void _fill( int n ) + throws MongoException { + _checkType( CursorType.ARRAY ); + while ( n >= _all.size() && _hasNext() ) + _next(); + } + + /** + * pulls back all items into an array and returns the number of objects. + * Note: this can be resource intensive + * @see #count() + * @see #size() + * @return the number of elements in the array + * @throws MongoException + */ + public int length() + throws MongoException { + _checkType( CursorType.ARRAY ); + _fill( Integer.MAX_VALUE ); + return _all.size(); + } + + /** + * Converts this cursor to an array. + * @return an array of elements + * @throws MongoException + */ + public List toArray() + throws MongoException { + return toArray( Integer.MAX_VALUE ); + } + + /** + * Converts this cursor to an array. + * @param max the maximum number of objects to return + * @return an array of objects + * @throws MongoException + */ + public List toArray( int max ) + throws MongoException { + _checkType( CursorType.ARRAY ); + _fill( max - 1 ); + return _all; + } + + /** + * for testing only! + * Iterates cursor and counts objects + * @see #count() + * @return num objects + */ + public int itcount(){ + int n = 0; + while ( this.hasNext() ){ + this.next(); + n++; + } + return n; + } + + /** + * Counts the number of objects matching the query + * This does not take limit/skip into consideration + * @see #size() + * @return the number of objects + * @throws MongoException + */ + public int count() + throws MongoException { + if ( _collection == null ) + throw new IllegalArgumentException( "why is _collection null" ); + if ( _collection._db == null ) + throw new IllegalArgumentException( "why is _collection._db null" ); + + return (int)_collection.getCount(this._query, this._keysWanted); + } + + /** + * Counts the number of objects matching the query + * this does take limit/skip into consideration + * @see #count() + * @return the number of objects + * @throws MongoException + */ + public int size() + throws MongoException { + if ( _collection == null ) + throw new IllegalArgumentException( "why is _collection null" ); + if ( _collection._db == null ) + throw new IllegalArgumentException( "why is _collection._db null" ); + + return (int)_collection.getCount(this._query, this._keysWanted, this._limit, this._skip ); + } + + + /** + * gets the fields to be returned + * @return + */ + public DBObject getKeysWanted(){ + return _keysWanted; + } + + /** + * gets the query + * @return + */ + public DBObject getQuery(){ + return _query; + } + + /** + * gets the collection + * @return + */ + public DBCollection getCollection(){ + return _collection; + } + + /** + * Gets the Server Address of the server that data is pulled from. + * Note that this information may not be available until hasNext() or next() is called. + * @return + */ + public ServerAddress getServerAddress() { + if (_it != null && _it instanceof DBApiLayer.Result) + return ((DBApiLayer.Result)_it).getServerAddress(); + + return null; + } + + /** + * Sets the read preference for this cursor. + * See the * documentation for {@link ReadPreference} + * for more information. + * + * @param preference Read Preference to use + */ + public DBCursor setReadPreference( ReadPreference preference ){ + _readPref = preference; + return this; + } + + /** + * Gets the default read preference + * @return + */ + public ReadPreference getReadPreference(){ + return _readPref; + } + + public DBCursor setDecoderFactory(DBDecoderFactory fact){ + _decoderFact = fact; + return this; + } + + public DBDecoderFactory getDecoderFactory(){ + return _decoderFact; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Cursor id=").append(getCursorId()); + sb.append(", ns=").append(getCollection().getFullName()); + sb.append(", query=").append(getQuery()); + if (getKeysWanted() != null) + sb.append(", fields=").append(getKeysWanted()); + sb.append(", numIterated=").append(_num); + if (_skip != 0) + sb.append(", skip=").append(_skip); + if (_limit != 0) + sb.append(", limit=").append(_limit); + if (_batchSize != 0) + sb.append(", batchSize=").append(_batchSize); + + ServerAddress addr = getServerAddress(); + if (addr != null) + sb.append(", addr=").append(addr); + + if (_readPref != null) + sb.append(", readPreference=").append( _readPref.toString() ); + return sb.toString(); + } + + // ---- query setup ---- + private final DBCollection _collection; + private final DBObject _query; + private final DBObject _keysWanted; + + private DBObject _orderBy = null; + private String _hint = null; + private DBObject _hintDBObj = null; + private boolean _explain = false; + private int _limit = 0; + private int _batchSize = 0; + private int _skip = 0; + private boolean _snapshot = false; + private int _options = 0; + private ReadPreference _readPref; + private DBDecoderFactory _decoderFact; + + private DBObject _specialFields; + + // ---- result info ---- + private Iterator _it = null; + + private CursorType _cursorType = null; + private DBObject _cur = null; + private int _num = 0; + + private final ArrayList _all = new ArrayList(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java new file mode 100644 index 00000000..95c519d5 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java @@ -0,0 +1,32 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.IOException; +import java.io.InputStream; + +import com.massivecraft.mcore3.lib.bson.BSONDecoder; + +/** + * + */ +public interface DBDecoder extends BSONDecoder { + public DBCallback getDBCallback(DBCollection collection); + + public DBObject decode( byte[] b, DBCollection collection ); + + public DBObject decode( InputStream in, DBCollection collection ) throws IOException; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java new file mode 100644 index 00000000..93e33c60 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * + */ +public interface DBDecoderFactory { + + public DBDecoder create( ); + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java new file mode 100644 index 00000000..e5ab3f02 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2008 - 2011 10gen, Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.*; +import com.massivecraft.mcore3.lib.bson.io.*; + +public interface DBEncoder { + public int writeObject( OutputBuffer buf, BSONObject o ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java new file mode 100644 index 00000000..b9c17eb3 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * + */ +public interface DBEncoderFactory { + + public DBEncoder create(); + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/DBObject.java new file mode 100644 index 00000000..5f253a90 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBObject.java @@ -0,0 +1,40 @@ +// DBObject.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.BSONObject; + +/** + * A key-value map that can be saved to the database. + */ +public interface DBObject extends BSONObject { + + /** + * if this object was retrieved with only some fields (using a field filter) + * this method will be called to mark it as such. + */ + public void markAsPartialObject(); + + /** + * whether markAsPartialObject was ever called + * only matters if you are going to upsert and do not want to risk losing fields + */ + public boolean isPartialObject(); + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java b/src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java new file mode 100644 index 00000000..af13129e --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java @@ -0,0 +1,60 @@ +// DBPointer.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +/** + * @deprecated + */ +@Deprecated +public class DBPointer extends DBRefBase { + + static final boolean D = Boolean.getBoolean( "DEBUG.DBPOINTER" ); + + /** + * CTOR used for testing BSON encoding. Otherwise + * non-functional due to a DBRef needing a parent db object, + * a fieldName and a db + * + * @param ns namespace to point to + * @param id value of _id + */ + public DBPointer(String ns, ObjectId id) { + this (null, null, null, ns, id); + } + + DBPointer( DBObject parent , String fieldName , DB db , String ns , ObjectId id ){ + super(db, ns, (Object)id); + + _parent = parent; + _fieldName = fieldName; + } + + public String toString(){ + return "{ \"$ref\" : \"" + _ns + "\", \"$id\" : ObjectId(\"" + _id + "\") }"; + } + + public ObjectId getId() { + return (ObjectId)_id; + } + + final DBObject _parent; + final String _fieldName; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBPort.java b/src/com/massivecraft/mcore3/lib/mongodb/DBPort.java new file mode 100644 index 00000000..8820cadd --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBPort.java @@ -0,0 +1,345 @@ +// DBPort.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.mongodb.util.ThreadUtil; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * represents a Port to the database, which is effectively a single connection to a server + * Methods implemented at the port level should throw the raw exceptions like IOException, + * so that the connector above can make appropriate decisions on how to handle. + */ +public class DBPort { + + /** + * the default port + */ + public static final int PORT = 27017; + static final boolean USE_NAGLE = false; + + static final long CONN_RETRY_TIME_MS = 15000; + + /** + * creates a new DBPort + * @param addr the server address + */ + public DBPort( ServerAddress addr ){ + this( addr , null , new MongoOptions() ); + } + + DBPort( ServerAddress addr, DBPortPool pool, MongoOptions options ){ + _options = options; + _sa = addr; + _addr = addr.getSocketAddress(); + _pool = pool; + + _hashCode = _addr.hashCode(); + + _logger = Logger.getLogger( _rootLogger.getName() + "." + addr.toString() ); + _decoder = _options.dbDecoderFactory.create(); + } + + Response call( OutMessage msg , DBCollection coll ) throws IOException{ + return go( msg, coll ); + } + + Response call( OutMessage msg , DBCollection coll , DBDecoder decoder) throws IOException{ + return go( msg, coll, false, null, decoder); + } + + Response call( OutMessage msg , DBCollection coll , ReadPreference readPref , DBDecoder decoder) throws IOException{ + return go( msg, coll, false, readPref, decoder); + } + + void say( OutMessage msg ) + throws IOException { + go( msg , null ); + } + + private synchronized Response go( OutMessage msg , DBCollection coll ) + throws IOException { + return go( msg , coll , false, null, null ); + } + + private synchronized Response go( OutMessage msg , DBCollection coll , DBDecoder decoder ) throws IOException{ + return go( msg, coll, false, null, decoder ); + } + + private synchronized Response go( OutMessage msg , DBCollection coll , boolean forceReponse , ReadPreference readPref, DBDecoder decoder) + throws IOException { + + if ( _processingResponse ){ + if ( coll == null ){ + // this could be a pipeline and should be safe + } + else { + // this could cause issues since we're reading data off the wire + throw new IllegalStateException( "DBPort.go called and expecting a response while processing another response" ); + } + } + + _calls++; + + if ( _socket == null ) + _open(); + + if ( _out == null ) + throw new IllegalStateException( "_out shouldn't be null" ); + + try { + msg.prepare(); + msg.pipe( _out ); + + if ( _pool != null ) + _pool._everWorked = true; + + if ( coll == null && ! forceReponse ) + return null; + + _processingResponse = true; + return new Response( _sa , coll , _in , (decoder == null ? _decoder : decoder) ); + } + catch ( IOException ioe ){ + close(); + throw ioe; + } + finally { + _processingResponse = false; + } + } + + synchronized CommandResult getLastError( DB db , WriteConcern concern ) throws IOException{ + DBApiLayer dbAL = (DBApiLayer) db; + return runCommand( dbAL, concern.getCommand() ); + } + + synchronized private Response findOne( DB db , String coll , DBObject q ) throws IOException { + OutMessage msg = OutMessage.query( db._mongo , 0 , db.getName() + "." + coll , 0 , -1 , q , null ); + Response res = go( msg , db.getCollection( coll ) , null ); + return res; + } + + @SuppressWarnings("unused") + synchronized private Response findOne( String ns , DBObject q ) throws IOException{ + OutMessage msg = OutMessage.query( null , 0 , ns , 0 , -1 , q , null ); + Response res = go( msg , null , true, null, null ); + return res; + } + + synchronized CommandResult runCommand( DB db , DBObject cmd ) throws IOException { + Response res = findOne( db , "$cmd" , cmd ); + return convertToCommandResult(cmd, res); + } + + private CommandResult convertToCommandResult(DBObject cmd, Response res) { + if ( res.size() == 0 ) + return null; + if ( res.size() > 1 ) + throw new MongoInternalException( "something is wrong. size:" + res.size() ); + + DBObject data = res.get(0); + if ( data == null ) + throw new MongoInternalException( "something is wrong, no command result" ); + + CommandResult cr = new CommandResult(cmd, res.serverUsed()); + cr.putAll( data ); + return cr; + } + + synchronized CommandResult tryGetLastError( DB db , long last, WriteConcern concern) throws IOException { + if ( last != _calls ) + return null; + + return getLastError( db , concern ); + } + + /** + * makes sure that a connection to the server has been opened + * @throws IOException + */ + public synchronized void ensureOpen() + throws IOException { + + if ( _socket != null ) + return; + + _open(); + } + + boolean _open() + throws IOException { + + long sleepTime = 100; + + long maxAutoConnectRetryTime = CONN_RETRY_TIME_MS; + if (_options.maxAutoConnectRetryTime > 0) { + maxAutoConnectRetryTime = _options.maxAutoConnectRetryTime; + } + + final long start = System.currentTimeMillis(); + while ( true ){ + + IOException lastError = null; + + try { + _socket = _options.socketFactory.createSocket(); + _socket.connect( _addr , _options.connectTimeout ); + + _socket.setTcpNoDelay( ! USE_NAGLE ); + _socket.setKeepAlive( _options.socketKeepAlive ); + _socket.setSoTimeout( _options.socketTimeout ); + _in = new BufferedInputStream( _socket.getInputStream() ); + _out = _socket.getOutputStream(); + return true; + } + catch ( IOException ioe ){ + lastError = new IOException( "couldn't connect to [" + _addr + "] bc:" + ioe ); + _logger.log( Level.INFO , "connect fail to : " + _addr , ioe ); + close(); + } + + if ( ! _options.autoConnectRetry || ( _pool != null && ! _pool._everWorked ) ) + throw lastError; + + long sleptSoFar = System.currentTimeMillis() - start; + + if ( sleptSoFar >= maxAutoConnectRetryTime ) + throw lastError; + + if ( sleepTime + sleptSoFar > maxAutoConnectRetryTime ) + sleepTime = maxAutoConnectRetryTime - sleptSoFar; + + _logger.severe( "going to sleep and retry. total sleep time after = " + ( sleptSoFar + sleptSoFar ) + "ms this time:" + sleepTime + "ms" ); + ThreadUtil.sleep( sleepTime ); + sleepTime *= 2; + + } + } + + @Override + public int hashCode(){ + return _hashCode; + } + + /** + * returns a String representation of the target host + * @return + */ + public String host(){ + return _addr.toString(); + } + + /** + * @return the server address for this port + */ + public ServerAddress serverAddress() { + return _sa; + } + + @Override + public String toString(){ + return "{DBPort " + host() + "}"; + } + + @Override + protected void finalize() throws Throwable{ + super.finalize(); + close(); + } + + /** + * closes the underlying connection and streams + */ + protected void close(){ + _authed.clear(); + + if ( _socket != null ){ + try { + _socket.close(); + } + catch ( Exception e ){ + // don't care + } + } + + _in = null; + _out = null; + _socket = null; + } + + void checkAuth( DB db ) throws IOException { + if ( db._username == null ){ + if ( db._name.equals( "admin" ) ) + return; + checkAuth( db._mongo.getDB( "admin" ) ); + return; + } + if ( _authed.containsKey( db ) ) + return; + + CommandResult res = runCommand( db , new BasicDBObject( "getnonce" , 1 ) ); + res.throwOnError(); + + DBObject temp = db._authCommand( res.getString( "nonce" ) ); + + res = runCommand( db , temp ); + + res.throwOnError(); + _authed.put( db , true ); + } + + /** + * Gets the pool that this port belongs to + * @return + */ + public DBPortPool getPool() { + return _pool; + } + + final int _hashCode; + final ServerAddress _sa; + final InetSocketAddress _addr; + final DBPortPool _pool; + final MongoOptions _options; + final Logger _logger; + final DBDecoder _decoder; + + private Socket _socket; + private InputStream _in; + private OutputStream _out; + + private boolean _processingResponse; + + private Map _authed = new ConcurrentHashMap( ); + int _lastThread; + long _calls = 0; + + private static Logger _rootLogger = Logger.getLogger( "com.mongodb.port" ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java b/src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java new file mode 100644 index 00000000..ca940d24 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java @@ -0,0 +1,252 @@ +// DBPortPool.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import com.massivecraft.mcore3.lib.mongodb.util.SimplePool; + +public class DBPortPool extends SimplePool { + + static class Holder { + + Holder( MongoOptions options ){ + _options = options; + { + MBeanServer temp = null; + try { + temp = ManagementFactory.getPlatformMBeanServer(); + } + catch ( Throwable t ){ + } + + _server = temp; + } + } + + DBPortPool get( ServerAddress addr ){ + + DBPortPool p = _pools.get( addr ); + + if (p != null) + return p; + + synchronized (_pools) { + p = _pools.get( addr ); + if (p != null) { + return p; + } + + p = new DBPortPool( addr , _options ); + _pools.put( addr , p); + + if ( _server != null ){ + try { + ObjectName on = createObjectName( addr ); + if ( _server.isRegistered( on ) ){ + _server.unregisterMBean( on ); + Bytes.LOGGER.log( Level.INFO , "multiple Mongo instances for same host, jmx numbers might be off" ); + } + _server.registerMBean( p , on ); + } + catch ( JMException e ){ + Bytes.LOGGER.log( Level.WARNING , "jmx registration error: " + e + " continuing..." ); + } + catch ( java.security.AccessControlException e ){ + Bytes.LOGGER.log( Level.WARNING , "jmx registration error: " + e + " continuing..." ); + } + } + + } + + return p; + } + + void close(){ + synchronized ( _pools ){ + for ( DBPortPool p : _pools.values() ){ + p.close(); + + try { + ObjectName on = createObjectName( p._addr ); + if ( _server.isRegistered( on ) ){ + _server.unregisterMBean( on ); + } + } catch ( JMException e ){ + Bytes.LOGGER.log( Level.WARNING , "jmx de-registration error, continuing" , e ); + } + } + } + } + + private ObjectName createObjectName( ServerAddress addr ) throws MalformedObjectNameException { + String name = "com.mongodb:type=ConnectionPool,host=" + addr.toString().replace( ":" , ",port=" ) + ",instance=" + _serial; + if ( _options.description != null ) + name += ",description=" + _options.description; + return new ObjectName( name ); + } + + final MongoOptions _options; + final Map _pools = Collections.synchronizedMap( new HashMap() ); + final MBeanServer _server; + final int _serial = nextSerial.incrementAndGet(); + + // we use this to give each Holder a different mbean name + static AtomicInteger nextSerial = new AtomicInteger(0); + } + + // ---- + + public static class NoMoreConnection extends MongoInternalException { + private static final long serialVersionUID = -4415279469780082174L; + + NoMoreConnection( String msg ){ + super( msg ); + } + } + + public static class SemaphoresOut extends NoMoreConnection { + private static final long serialVersionUID = -4415279469780082174L; + SemaphoresOut(){ + super( "Out of semaphores to get db connection" ); + } + } + + public static class ConnectionWaitTimeOut extends NoMoreConnection { + private static final long serialVersionUID = -4415279469780082174L; + ConnectionWaitTimeOut(int timeout) { + super("Connection wait timeout after " + timeout + " ms"); + } + } + + // ---- + + DBPortPool( ServerAddress addr , MongoOptions options ){ + super( "DBPortPool-" + addr.toString() + ", options = " + options.toString() , options.connectionsPerHost , options.connectionsPerHost ); + _options = options; + _addr = addr; + _waitingSem = new Semaphore( _options.connectionsPerHost * _options.threadsAllowedToBlockForConnectionMultiplier ); + } + + protected long memSize( DBPort p ){ + return 0; + } + + protected int pick( int iThink , boolean couldCreate ){ + final int id = System.identityHashCode(Thread.currentThread()); + final int s = _availSafe.size(); + for ( int i=0; i all = new ArrayList(); + while ( true ){ + DBPort temp = get(0); + if ( temp == null ) + break; + all.add( temp ); + } + + for ( DBPort p : all ){ + p.close(); + done(p); + } + + } + + void close(){ + clear(); + } + + public void cleanup( DBPort p ){ + p.close(); + } + + public boolean ok( DBPort t ){ + return _addr.getSocketAddress().equals( t._addr ); + } + + protected DBPort createNew(){ + return new DBPort( _addr , this , _options ); + } + + public ServerAddress getServerAddress() { + return _addr; + } + + final MongoOptions _options; + final private Semaphore _waitingSem; + final ServerAddress _addr; + boolean _everWorked = false; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBRef.java b/src/com/massivecraft/mcore3/lib/mongodb/DBRef.java new file mode 100644 index 00000000..61ab66b3 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBRef.java @@ -0,0 +1,65 @@ +// DBRef.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.BSONObject; + +/** + * overrides DBRefBase to understand a BSONObject representation of a reference. + * @dochub dbrefs + */ +public class DBRef extends DBRefBase { + + static final boolean D = Boolean.getBoolean( "DEBUG.DBREF" ); + + /** + * Creates a DBRef + * @param db the database + * @param o a BSON object representing the reference + */ + public DBRef(DB db , BSONObject o ){ + super( db , o.get( "$ref" ).toString() , o.get( "$id" ) ); + } + + /** + * Creates a DBRef + * @param db the database + * @param ns the namespace where the object is stored + * @param id the object id + */ + public DBRef(DB db , String ns , Object id) { + super(db, ns, id); + } + + /** + * fetches a referenced object from the database + * @param db the database + * @param ref the reference + * @return + */ + public static DBObject fetch(DB db, DBObject ref) { + String ns; + Object id; + + if ((ns = (String)ref.get("$ref")) != null && (id = ref.get("$id")) != null) { + return db.getCollection(ns).findOne(new BasicDBObject("_id", id)); + } + return null; + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java b/src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java new file mode 100644 index 00000000..8b14a569 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java @@ -0,0 +1,105 @@ +// DBRefBase.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * represents a database reference, which points to an object stored in the database + */ +public class DBRefBase { + + + /** + * Creates a DBRefBase + * @param db the database + * @param ns the namespace where the object is stored + * @param id the object id + */ + public DBRefBase(DB db , String ns , Object id) { + _db = db; + _ns = ns.intern(); + _id = id; + } + + /** + * fetches the object referenced from the database + * @return + */ + public DBObject fetch() { + if (_loadedPointedTo) + return _pointedTo; + + if (_db == null) + throw new RuntimeException("no db"); + + final DBCollection coll = _db.getCollectionFromString(_ns); + + _pointedTo = coll.findOne(_id); + _loadedPointedTo = true; + return _pointedTo; + } + + @Override + public String toString(){ + return "{ \"$ref\" : \"" + _ns + "\", \"$id\" : \"" + _id + "\" }"; + } + + /** + * Gets the object's id + * @return + */ + public Object getId() { + return _id; + } + + /** + * Gets the object's namespace (collection name) + * @return + */ + public String getRef() { + return _ns; + } + + /** + * Gets the database + * @return + */ + public DB getDB() { + return _db; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + + if (obj instanceof DBRefBase) { + DBRefBase ref = (DBRefBase) obj; + if (_ns.equals(ref.getRef()) && _id.equals(ref.getId())) + return true; + } + return false; + } + + final Object _id; + final String _ns; + final DB _db; + + private boolean _loadedPointedTo = false; + private DBObject _pointedTo; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java b/src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java new file mode 100644 index 00000000..167baa69 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java @@ -0,0 +1,581 @@ +// DBTCPConnector.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.mongodb.ReadPreference.TaggedReadPreference; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DBTCPConnector implements DBConnector { + + static Logger _logger = Logger.getLogger( Bytes.LOGGER.getName() + ".tcp" ); + static Logger _createLogger = Logger.getLogger( _logger.getName() + ".connect" ); + + public DBTCPConnector( Mongo m , ServerAddress addr ) + throws MongoException { + _mongo = m; + _portHolder = new DBPortPool.Holder( m._options ); + _checkAddress( addr ); + + _createLogger.info( addr.toString() ); + + setMasterAddress(addr); + _allHosts = null; + _rsStatus = null; + + } + + public DBTCPConnector( Mongo m , ServerAddress ... all ) + throws MongoException { + this( m , Arrays.asList( all ) ); + } + + public DBTCPConnector( Mongo m , List all ) + throws MongoException { + _mongo = m; + _portHolder = new DBPortPool.Holder( m._options ); + _checkAddress( all ); + + _allHosts = new ArrayList( all ); // make a copy so it can't be modified + _rsStatus = new ReplicaSetStatus( m, _allHosts ); + + _createLogger.info( all + " -> " + getAddress() ); + } + + public void start() { + if (_rsStatus != null) + _rsStatus.start(); + } + + private static ServerAddress _checkAddress( ServerAddress addr ){ + if ( addr == null ) + throw new NullPointerException( "address can't be null" ); + return addr; + } + + private static ServerAddress _checkAddress( List addrs ){ + if ( addrs == null ) + throw new NullPointerException( "addresses can't be null" ); + if ( addrs.size() == 0 ) + throw new IllegalArgumentException( "need to specify at least 1 address" ); + return addrs.get(0); + } + + /** + * Start a "request". + * + * A "request" is a group of operations in which order matters. Examples + * include inserting a document and then performing a query which expects + * that document to have been inserted, or performing an operation and + * then using com.mongodb.Mongo.getLastError to perform error-checking + * on that operation. When a thread performs operations in a "request", all + * operations will be performed on the same socket, so they will be + * correctly ordered. + */ + @Override + public void requestStart(){ + _myPort.get().requestStart(); + } + + /** + * End the current "request", if this thread is in one. + * + * By ending a request when it is safe to do so the built-in connection- + * pool is allowed to reassign requests to different sockets in order to + * more effectively balance load. See requestStart for more information. + */ + @Override + public void requestDone(){ + _myPort.get().requestDone(); + } + + @Override + public void requestEnsureConnection(){ + _myPort.get().requestEnsureConnection(); + } + + void _checkClosed(){ + if ( _closed.get() ) + throw new IllegalStateException( "this Mongo has been closed" ); + } + + WriteResult _checkWriteError( DB db, DBPort port , WriteConcern concern ) + throws MongoException, IOException { + CommandResult e = port.runCommand( db , concern.getCommand() ); + + e.throwOnError(); + return new WriteResult( e , concern ); + } + + @Override + public WriteResult say( DB db , OutMessage m , WriteConcern concern ) + throws MongoException { + return say( db , m , concern , null ); + } + + @Override + public WriteResult say( DB db , OutMessage m , WriteConcern concern , ServerAddress hostNeeded ) + throws MongoException { + + _checkClosed(); + checkMaster( false , true ); + + MyPort mp = _myPort.get(); + DBPort port = mp.get( true , ReadPreference.PRIMARY, hostNeeded ); + + try { + port.checkAuth( db ); + port.say( m ); + if ( concern.callGetLastError() ){ + return _checkWriteError( db , port , concern ); + } + else { + return new WriteResult( db , port , concern ); + } + } + catch ( IOException ioe ){ + mp.error( port , ioe ); + _error( ioe, false ); + + if ( concern.raiseNetworkErrors() ) + throw new MongoException.Network( "can't say something" , ioe ); + + CommandResult res = new CommandResult(port.serverAddress()); + res.put( "ok" , false ); + res.put( "$err" , "NETWORK ERROR" ); + return new WriteResult( res , concern ); + } + catch ( MongoException me ){ + throw me; + } + catch ( RuntimeException re ){ + mp.error( port , re ); + throw re; + } + finally { + mp.done( port ); + m.doneWithMessage(); + } + } + + @Override + public Response call( DB db , DBCollection coll , OutMessage m, ServerAddress hostNeeded, DBDecoder decoder ) + throws MongoException { + return call( db , coll , m , hostNeeded , 2, null, decoder ); + } + + + public Response call( DB db , DBCollection coll , OutMessage m , ServerAddress hostNeeded , int retries ) throws MongoException { + return call( db, coll, m, hostNeeded, retries, null, null); + } + + @Override + public Response call( DB db, DBCollection coll, OutMessage m, ServerAddress hostNeeded, int retries, ReadPreference readPref, DBDecoder decoder ) throws MongoException{ + + if (readPref == null) + readPref = ReadPreference.PRIMARY; + + if (readPref == ReadPreference.PRIMARY && m.hasOption( Bytes.QUERYOPTION_SLAVEOK )) + readPref = ReadPreference.SECONDARY; + + boolean secondaryOk = !(readPref == ReadPreference.PRIMARY); + + _checkClosed(); + checkMaster( false, !secondaryOk ); + + final MyPort mp = _myPort.get(); + final DBPort port = mp.get( false , readPref, hostNeeded ); + + Response res = null; + boolean retry = false; + try { + port.checkAuth( db ); + res = port.call( m , coll, readPref, decoder ); + if ( res._responseTo != m.getId() ) + throw new MongoException( "ids don't match" ); + } + catch ( IOException ioe ){ + mp.error( port , ioe ); + retry = retries > 0 && !coll._name.equals( "$cmd" ) + && !(ioe instanceof SocketTimeoutException) && _error( ioe, secondaryOk ); + if ( !retry ){ + throw new MongoException.Network( "can't call something : " + port.host() + "/" + db, + ioe ); + } + } + catch ( RuntimeException re ){ + mp.error( port , re ); + throw re; + } finally { + mp.done( port ); + } + + if (retry) + return call( db , coll , m , hostNeeded , retries - 1 , readPref, decoder ); + + ServerError err = res.getError(); + + if ( err != null && err.isNotMasterError() ){ + checkMaster( true , true ); + if ( retries <= 0 ){ + throw new MongoException( "not talking to master and retries used up" ); + } + return call( db , coll , m , hostNeeded , retries -1, readPref, decoder ); + } + + m.doneWithMessage(); + return res; + } + + public ServerAddress getAddress(){ + DBPortPool pool = _masterPortPool; + return pool != null ? pool.getServerAddress() : null; + } + + /** + * Gets the list of seed server addresses + * @return + */ + public List getAllAddress() { + return _allHosts; + } + + /** + * Gets the list of server addresses currently seen by the connector. + * This includes addresses auto-discovered from a replica set. + * @return + */ + public List getServerAddressList() { + if (_rsStatus != null) { + return _rsStatus.getServerAddressList(); + } + + ServerAddress master = getAddress(); + if (master != null) { + // single server + List list = new ArrayList(); + list.add(master); + return list; + } + return null; + } + + public ReplicaSetStatus getReplicaSetStatus() { + return _rsStatus; + } + + public String getConnectPoint(){ + ServerAddress master = getAddress(); + return master != null ? master.toString() : null; + } + + /** + * This method is called in case of an IOException. + * It will potentially trigger a checkMaster() to check the status of all servers. + * @param t the exception thrown + * @param secondaryOk secondaryOk flag + * @return true if the request should be retried, false otherwise + * @throws MongoException + */ + boolean _error( Throwable t, boolean secondaryOk ) + throws MongoException { + if (_rsStatus == null) { + // single server, no need to retry + return false; + } + + // the replset has at least 1 server up, try to see if should switch master + // if no server is up, we wont retry until the updater thread finds one + // this is to cut down the volume of requests/errors when all servers are down + if ( _rsStatus.hasServerUp() ){ + checkMaster( true , !secondaryOk ); + } + return _rsStatus.hasServerUp(); + } + + class MyPort { + + DBPort get( boolean keep , ReadPreference readPref, ServerAddress hostNeeded ){ + + if ( hostNeeded != null ){ + if (_requestPort != null && _requestPort.serverAddress().equals(hostNeeded)) { + return _requestPort; + } + + // asked for a specific host + return _portHolder.get( hostNeeded ).get(); + } + + if ( _requestPort != null ){ + // we are within a request, and have a port, should stick to it + if ( _requestPort.getPool() == _masterPortPool || !keep ) { + // if keep is false, it's a read, so we use port even if master changed + return _requestPort; + } + + // it's write and master has changed + // we fall back on new master and try to go on with request + // this may not be best behavior if spec of request is to stick with same server + _requestPort.getPool().done(_requestPort); + _requestPort = null; + } + + if ( !(readPref == ReadPreference.PRIMARY) && _rsStatus != null ){ + // if not a primary read set, try to use a secondary + // Do they want a Secondary, or a specific tag set? + if (readPref == ReadPreference.SECONDARY) { + ServerAddress slave = _rsStatus.getASecondary(); + if ( slave != null ){ + return _portHolder.get( slave ).get(); + } + } else if (readPref instanceof ReadPreference.TaggedReadPreference) { + // Tag based read + ServerAddress secondary = _rsStatus.getASecondary( ( (TaggedReadPreference) readPref ).getTags() ); + if (secondary != null) + return _portHolder.get( secondary ).get(); + else + throw new MongoException( "Could not find any valid secondaries with the supplied tags ('" + + ( (TaggedReadPreference) readPref ).getTags() + "'"); + } + } + + if (_masterPortPool == null) { + // this should only happen in rare case that no master was ever found + // may get here at startup if it's a read, slaveOk=true, and ALL servers are down + throw new MongoException("Rare case where master=null, probably all servers are down"); + } + + // use master + DBPort p = _masterPortPool.get(); + if ( _inRequest ) { + // if within request, remember port to stick to same server + _requestPort = p; + } + + return p; + } + + void done( DBPort p ){ + // keep request port + if ( p != _requestPort ){ + p.getPool().done(p); + } + } + + /** + * call this method when there is an IOException or other low level error on port. + * @param p + * @param e + */ + void error( DBPort p , Exception e ){ + p.close(); + _requestPort = null; +// _logger.log( Level.SEVERE , "MyPort.error called" , e ); + + // depending on type of error, may need to close other connections in pool + p.getPool().gotError(e); + } + + void requestEnsureConnection(){ + if ( ! _inRequest ) + return; + + if ( _requestPort != null ) + return; + + _requestPort = _masterPortPool.get(); + } + + void requestStart(){ + _inRequest = true; + } + + void requestDone(){ + if ( _requestPort != null ) + _requestPort.getPool().done( _requestPort ); + _requestPort = null; + _inRequest = false; + } + + DBPort _requestPort; +// DBPortPool _requestPool; + boolean _inRequest; + } + + void checkMaster( boolean force , boolean failIfNoMaster ) + throws MongoException { + + if ( _rsStatus != null ){ + if ( _masterPortPool == null || force ){ + ReplicaSetStatus.Node master = _rsStatus.ensureMaster(); + if ( master == null ){ + if ( failIfNoMaster ) + throw new MongoException( "can't find a master" ); + } + else { + setMaster(master); + } + } + } else { + // single server, may have to obtain max bson size + if (_maxBsonObjectSize.get() == 0) + fetchMaxBsonObjectSize(); + } + } + + synchronized void setMaster(ReplicaSetStatus.Node master) { + if (_closed.get()) { + return; + } + setMasterAddress(master.getServerAddress()); + _maxBsonObjectSize.set(master.getMaxBsonObjectSize()); + } + + /** + * Fetches the maximum size for a BSON object from the current master server + * @return the size, or 0 if it could not be obtained + */ + int fetchMaxBsonObjectSize() { + if (_masterPortPool == null) + return 0; + DBPort port = _masterPortPool.get(); + try { + CommandResult res = port.runCommand(_mongo.getDB("admin"), new BasicDBObject("isMaster", 1)); + // max size was added in 1.8 + if (res.containsField("maxBsonObjectSize")) { + _maxBsonObjectSize.set(((Integer) res.get("maxBsonObjectSize")).intValue()); + } else { + _maxBsonObjectSize.set(Bytes.MAX_OBJECT_SIZE); + } + } catch (Exception e) { + _logger.log(Level.WARNING, "Exception determining maxBSONObjectSize ", e); + } finally { + port.getPool().done(port); + } + + return _maxBsonObjectSize.get(); + } + + + + private synchronized boolean setMasterAddress(ServerAddress addr) { + DBPortPool newPool = _portHolder.get( addr ); + if (newPool == _masterPortPool) + return false; + + if ( _masterPortPool != null ) + _logger.log(Level.WARNING, "Master switching from " + _masterPortPool.getServerAddress() + " to " + addr); + _masterPortPool = newPool; + return true; + } + + public String debugString(){ + StringBuilder buf = new StringBuilder( "DBTCPConnector: " ); + if ( _rsStatus != null ) { + buf.append( "replica set : " ).append( _allHosts ); + } else { + ServerAddress master = getAddress(); + buf.append( master ).append( " " ).append( master != null ? master.getSocketAddress() : null ); + } + + return buf.toString(); + } + + public void close(){ + _closed.set( true ); + if ( _portHolder != null ) { + try { + _portHolder.close(); + _portHolder = null; + } catch (final Throwable t) { /* nada */ } + } + if ( _rsStatus != null ) { + try { + _rsStatus.close(); + _rsStatus = null; + } catch (final Throwable t) { /* nada */ } + } + + // below this will remove the myport for this thread only + // client using thread pool in web framework may need to call close() from all threads + _myPort.remove(); + } + + /** + * Assigns a new DBPortPool for a given ServerAddress. + * This is used to obtain a new pool when the resolved IP of a host changes, for example. + * User application should not have to call this method directly. + * @param addr + */ + public void updatePortPool(ServerAddress addr) { + // just remove from map, a new pool will be created lazily + _portHolder._pools.remove(addr); + } + + /** + * Gets the DBPortPool associated with a ServerAddress. + * @param addr + * @return + */ + public DBPortPool getDBPortPool(ServerAddress addr) { + return _portHolder.get(addr); + } + + public boolean isOpen(){ + return ! _closed.get(); + } + + /** + * Gets the maximum size for a BSON object supported by the current master server. + * Note that this value may change over time depending on which server is master. + * @return the maximum size, or 0 if not obtained from servers yet. + */ + public int getMaxBsonObjectSize() { + return _maxBsonObjectSize.get(); + } + + // expose for unit testing + MyPort getMyPort() { + return _myPort.get(); + } + + private volatile DBPortPool _masterPortPool; + private final Mongo _mongo; + private DBPortPool.Holder _portHolder; + private final List _allHosts; + private ReplicaSetStatus _rsStatus; + private final AtomicBoolean _closed = new AtomicBoolean(false); + + private final AtomicInteger _maxBsonObjectSize = new AtomicInteger(0); + + private ThreadLocal _myPort = new ThreadLocal(){ + protected MyPort initialValue(){ + return new MyPort(); + } + }; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java new file mode 100644 index 00000000..b476a020 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java @@ -0,0 +1,142 @@ +// DBCallback.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +// Bson +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.BasicBSONCallback; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +/** + * This class overrides BasicBSONCallback to implement some extra features specific to the Database. + * For example DBRef type. + * @author antoine + */ +public class DefaultDBCallback extends BasicBSONCallback implements DBCallback { + + static class DefaultFactory implements DBCallbackFactory { + @Override + public DBCallback create( DBCollection collection ){ + return new DefaultDBCallback( collection ); + } + } + + public static DBCallbackFactory FACTORY = new DefaultFactory(); + + public DefaultDBCallback( DBCollection coll ){ + _collection = coll; + _db = _collection == null ? null : _collection.getDB(); + } + + @Override + @SuppressWarnings("deprecation") + public void gotDBRef( String name , String ns , ObjectId id ){ + if ( id.equals( Bytes.COLLECTION_REF_ID ) ) + cur().put( name , _collection ); + else + cur().put( name , new DBPointer( (DBObject)cur() , name , _db , ns , id ) ); + } + + @Override + public void objectStart(boolean array, String name){ + _lastName = name; + super.objectStart( array , name ); + } + + @Override + public Object objectDone(){ + BSONObject o = (BSONObject)super.objectDone(); + if ( ! ( o instanceof List ) && + o.containsField( "$ref" ) && + o.containsField( "$id" ) ){ + return cur().put( _lastName , new DBRef( _db, o ) ); + } + + return o; + } + + @Override + public BSONObject create(){ + return _create( null ); + } + + @Override + public BSONObject create( boolean array , List path ){ + if ( array ) + return new BasicDBList(); + return _create( path ); + } + + @SuppressWarnings("rawtypes") + private DBObject _create( List path ){ + + Class c = null; + + if ( _collection != null && _collection._objectClass != null){ + if ( path == null || path.size() == 0 ){ + c = _collection._objectClass; + } + else { + StringBuilder buf = new StringBuilder(); + for ( int i=0; i 0 ) + buf.append("."); + buf.append( path.get(i) ); + } + c = _collection.getInternalClass( buf.toString() ); + } + + } + + if ( c != null ){ + try { + return (DBObject)c.newInstance(); + } + catch ( InstantiationException ie ){ + LOGGER.log( Level.FINE , "can't create a: " + c , ie ); + throw new MongoInternalException( "can't instantiate a : " + c , ie ); + } + catch ( IllegalAccessException iae ){ + LOGGER.log( Level.FINE , "can't create a: " + c , iae ); + throw new MongoInternalException( "can't instantiate a : " + c , iae ); + } + } + + return new BasicDBObject(); + } + + DBObject dbget(){ + return (DBObject)get(); + } + + @Override + public void reset(){ + _lastName = null; + super.reset(); + } + + private String _lastName; + final DBCollection _collection; + final DB _db; + static final Logger LOGGER = Logger.getLogger( "com.mongo.DECODING" ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java new file mode 100644 index 00000000..0c8b7afb --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.IOException; +import java.io.InputStream; + +import com.massivecraft.mcore3.lib.bson.BasicBSONDecoder; + +/** + * + * @author antoine + */ +public class DefaultDBDecoder extends BasicBSONDecoder implements DBDecoder { + + static class DefaultFactory implements DBDecoderFactory { + @Override + public DBDecoder create( ){ + return new DefaultDBDecoder( ); + } + } + + public static DBDecoderFactory FACTORY = new DefaultFactory(); + + public DefaultDBDecoder( ){ + } + + public DBCallback getDBCallback(DBCollection collection) { + // brand new callback every time + return new DefaultDBCallback(collection); + } + + public DBObject decode(byte[] b, DBCollection collection) { + DBCallback cbk = getDBCallback(collection); + cbk.reset(); + decode(b, cbk); + return (DBObject) cbk.get(); + } + + public DBObject decode(InputStream in, DBCollection collection) throws IOException { + DBCallback cbk = getDBCallback(collection); + cbk.reset(); + decode(in, cbk); + return (DBObject) cbk.get(); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java new file mode 100644 index 00000000..d9a4077d --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2008 - 2011 10gen, Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import static com.massivecraft.mcore3.lib.bson.BSON.EOO; +import static com.massivecraft.mcore3.lib.bson.BSON.OBJECT; +import static com.massivecraft.mcore3.lib.bson.BSON.REF; + +import com.massivecraft.mcore3.lib.bson.*; +import com.massivecraft.mcore3.lib.bson.io.*; +import com.massivecraft.mcore3.lib.bson.types.*; + + +public class DefaultDBEncoder extends BasicBSONEncoder implements DBEncoder { + + public int writeObject( OutputBuffer buf, BSONObject o ){ + set( buf ); + int x = super.putObject( o ); + done(); + return x; + } + + static class DefaultFactory implements DBEncoderFactory { + @Override + public DBEncoder create( ){ + return new DefaultDBEncoder( ); + } + } + + @SuppressWarnings("deprecation") + protected boolean putSpecial( String name , Object val ){ + if ( val instanceof DBPointer ){ + DBPointer r = (DBPointer)val; + putDBPointer( name , r._ns , (ObjectId)r._id ); + return true; + } + + if ( val instanceof DBRefBase ){ + putDBRef( name, (DBRefBase)val ); + return true; + } + + return false; + } + + protected void putDBPointer( String name , String ns , ObjectId oid ){ + _put( REF , name ); + + _putValueString( ns ); + _buf.writeInt( oid._time() ); + _buf.writeInt( oid._machine() ); + _buf.writeInt( oid._inc() ); + } + + protected void putDBRef( String name, DBRefBase ref ){ + _put( OBJECT , name ); + final int sizePos = _buf.getPosition(); + _buf.writeInt( 0 ); + + _putObjectField( "$ref" , ref.getRef() ); + _putObjectField( "$id" , ref.getId() ); + + _buf.write( EOO ); + _buf.writeInt( sizePos , _buf.getPosition() - sizePos ); + } + + + public static DBEncoderFactory FACTORY = new DefaultFactory(); + + public DefaultDBEncoder( ){ + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java b/src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java new file mode 100644 index 00000000..1c72df00 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * This class groups the argument for a group operation and can build the underlying command object + * @dochub mapreduce + */ +public class GroupCommand { + + public GroupCommand(DBCollection inputCollection, DBObject keys, DBObject condition, DBObject initial, String reduce, String finalize) { + this.input = inputCollection.getName(); + this.keys = keys; + this.condition = condition; + this.initial = initial; + this.reduce = reduce; + this.finalize = finalize; + } + + public DBObject toDBObject() { + BasicDBObject args = new BasicDBObject(); + args.put( "ns" , input ); + args.put( "key" , keys ); + args.put( "cond" , condition ); + args.put( "$reduce" , reduce ); + args.put( "initial" , initial ); + if ( finalize != null ) + args.put( "finalize" , finalize ); + return new BasicDBObject( "group" , args ); + } + + String input; + DBObject keys; + DBObject condition; + DBObject initial; + String reduce; + String finalize; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java new file mode 100644 index 00000000..7da326de --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.Iterator; +import java.util.logging.Logger; + +import com.massivecraft.mcore3.lib.bson.LazyBSONCallback; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +/** + * + */ +public class LazyDBCallback extends LazyBSONCallback implements DBCallback { + + public LazyDBCallback( DBCollection coll ){ + _collection = coll; + _db = _collection == null ? null : _collection.getDB(); + } + + @SuppressWarnings("rawtypes") + @Override + public Object createObject( byte[] data, int offset ){ + LazyDBObject o = new LazyDBObject( data, offset, this ); + //log.info("Created inner BSONObject: " + o); + // need to detect DBRef but must make sure we dont search through all fields + // $ref must be 1st key + Iterator it = o.keySet().iterator(); + if ( it.hasNext() && it.next().equals( "$ref" ) && + o.containsField( "$id" ) ){ + return new DBRef( _db, o ); + } + return o; + } + + public Object createDBRef( String ns, ObjectId id ){ + return new DBRef( _db, ns, id ); + } + + final DBCollection _collection; + final DB _db; + @SuppressWarnings("unused") + private static final Logger log = Logger.getLogger( LazyDBCallback.class.getName() ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java new file mode 100644 index 00000000..24ea38fd --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.IOException; +import java.io.InputStream; + +import com.massivecraft.mcore3.lib.bson.LazyBSONDecoder; + +/** + * + */ +public class LazyDBDecoder extends LazyBSONDecoder implements DBDecoder { + static class LazyDBDecoderFactory implements DBDecoderFactory { + @Override + public DBDecoder create( ){ + return new LazyDBDecoder(); + } + } + + public static DBDecoderFactory FACTORY = new LazyDBDecoderFactory(); + + public LazyDBDecoder( ){ + } + + public DBCallback getDBCallback(DBCollection collection) { + // callback doesnt do anything special, could be unique per decoder + // but have to create per collection due to DBRef, at least + return new LazyDBCallback(collection); + } + + public DBObject decode(byte[] b, DBCollection collection) { + DBCallback cbk = getDBCallback(collection); + cbk.reset(); + decode(b, cbk); + return (DBObject) cbk.get(); + } + + public DBObject decode(InputStream in, DBCollection collection) throws IOException { + DBCallback cbk = getDBCallback(collection); + cbk.reset(); + decode(in, cbk); + return (DBObject) cbk.get(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java new file mode 100644 index 00000000..688a3cf4 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2008 - 2011 10gen, Inc. + *

    + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.io.OutputBuffer; + +import java.io.IOException; + +/** + * Encoder that only knows how to encode BSONObject instances of type LazyDBObject. + */ +public class LazyDBEncoder implements DBEncoder { + @Override + public int writeObject(final OutputBuffer buf, BSONObject o) { + if (!(o instanceof LazyDBObject)) { + throw new IllegalArgumentException("LazyDBEncoder can only encode BSONObject instances of type LazyDBObject"); + } + + LazyDBObject lazyDBObject = (LazyDBObject) o; + + try { + lazyDBObject.pipe(buf); + } catch (IOException e) { + throw new MongoException("Exception serializing a LazyDBObject", e); + } + + return lazyDBObject.getBSONSize(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java new file mode 100644 index 00000000..97ad0770 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.LazyBSONCallback; +import com.massivecraft.mcore3.lib.bson.LazyBSONObject; +import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer; + +public class LazyDBObject extends LazyBSONObject implements DBObject { + + public void markAsPartialObject() { + _partial = true; + } + + public boolean isPartialObject() { + return _partial; + } + + public LazyDBObject(BSONByteBuffer buff, LazyBSONCallback cbk){ + super(buff, cbk); + } + + public LazyDBObject(BSONByteBuffer buff, int offset, LazyBSONCallback cbk){ + super(buff, offset, cbk); + } + + + public LazyDBObject(byte[] data, LazyBSONCallback cbk){ + this(data, 0, cbk); + } + + public LazyDBObject(byte[] data, int offset, LazyBSONCallback cbk){ + super(data, offset, cbk); + } + + private boolean _partial = false; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java new file mode 100644 index 00000000..132d7dad --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.Iterator; +import java.util.logging.Logger; + +/** + * + */ +public class LazyWriteableDBCallback extends LazyDBCallback { + + public LazyWriteableDBCallback( DBCollection coll ){ + super(coll); + } + + @SuppressWarnings("rawtypes") + @Override + public Object createObject( byte[] data, int offset ){ + LazyWriteableDBObject o = new LazyWriteableDBObject( data, offset, this ); + //log.info("Created inner BSONObject: " + o); + // need to detect DBRef but must make sure we dont search through all fields + // $ref must be 1st key + Iterator it = o.keySet().iterator(); + if ( it.hasNext() && it.next().equals( "$ref" ) && + o.containsField( "$id" ) ){ + return new DBRef( _db, o ); + } + return o; + } + + @SuppressWarnings("unused") + private static final Logger log = Logger.getLogger( LazyWriteableDBCallback.class.getName() ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java new file mode 100644 index 00000000..702d8555 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + + +/** + * + */ +public class LazyWriteableDBDecoder extends LazyDBDecoder { + static class LazyDBDecoderFactory implements DBDecoderFactory { + @Override + public DBDecoder create( ){ + return new LazyWriteableDBDecoder(); + } + } + + public static DBDecoderFactory FACTORY = new LazyDBDecoderFactory(); + + public DBCallback getDBCallback(DBCollection collection) { + return new LazyWriteableDBCallback(collection); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java new file mode 100644 index 00000000..da23710d --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java @@ -0,0 +1,120 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.LazyBSONCallback; +import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer; + +public class LazyWriteableDBObject extends LazyDBObject { + + public LazyWriteableDBObject(BSONByteBuffer buff, LazyBSONCallback cbk){ + super(buff, cbk); + } + + public LazyWriteableDBObject(BSONByteBuffer buff, int offset, LazyBSONCallback cbk){ + super(buff, offset, cbk); + } + + + public LazyWriteableDBObject(byte[] data, LazyBSONCallback cbk){ + this(data, 0, cbk); + } + + public LazyWriteableDBObject(byte[] data, int offset, LazyBSONCallback cbk){ + super(data, offset, cbk); + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#put(java.lang.String, java.lang.Object) + */ + @Override + public Object put(String key, Object v) { + return writeable.put(key, v); + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#putAll(org.bson.BSONObject) + */ + @Override + public void putAll(BSONObject o) { + for(String key : o.keySet()){ + put(key, o.get(key)); + } + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#putAll(java.util.Map) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void putAll(Map m) { + writeable.putAll(m); + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#get(java.lang.String) + */ + @Override + public Object get(String key) { + Object o = writeable.get(key); + return (o!=null) ? o : super.get(key); + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#removeField(java.lang.String) + */ + @Override + public Object removeField(String key) { + Object o = writeable.remove(key); + return (o!=null) ? o : super.removeField(key); + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#containsField(java.lang.String) + */ + @Override + public boolean containsField(String s) { + boolean has = writeable.containsKey(s); + return (has) ? has : super.containsField(s); + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#keySet() + */ + @Override + public Set keySet() { + Set combined = new HashSet(); + combined.addAll(writeable.keySet()); + combined.addAll(super.keySet()); + return combined; + } + + /* (non-Javadoc) + * @see org.bson.LazyBSONObject#isEmpty() + */ + @Override + public boolean isEmpty() { + return writeable.isEmpty() || super.isEmpty(); + } + + final private HashMap writeable = new HashMap(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/MapReduceCommand.java b/src/com/massivecraft/mcore3/lib/mongodb/MapReduceCommand.java new file mode 100644 index 00000000..b8a65d33 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/MapReduceCommand.java @@ -0,0 +1,327 @@ +/** + * Copyright (c) 2010 10gen, Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.Map; + +/** + * This class groups the argument for a map/reduce operation and can build the underlying command object + * @dochub mapreduce + */ +public class MapReduceCommand { + + /** + * INLINE - Return results inline, no result is written to the DB server + * REPLACE - Save the job output to a collection, replacing its previous content + * MERGE - Merge the job output with the existing contents of outputTarget collection + * REDUCE - Reduce the job output with the existing contents of outputTarget collection + */ + public static enum OutputType { + REPLACE, MERGE, REDUCE, INLINE + }; + + /** + * Represents the command for a map reduce operation + * Runs the command in REPLACE output type to a named collection + * + * @param inputCollection + * the collection to read from + * @param map + * map function in javascript code + * @param reduce + * reduce function in javascript code + * @param outputCollection + * optional - leave null if want to get the result inline + * @param type + * the type of output + * @param query + * the query to use on input + * @return + * @throws MongoException + * @dochub mapreduce + */ + public MapReduceCommand(DBCollection inputCollection , String map , String reduce , String outputCollection, OutputType type, DBObject query) throws MongoException { + _input = inputCollection.getName(); + _map = map; + _reduce = reduce; + _outputTarget = outputCollection; + _outputType = type; + _query = query; + } + + /** + * Sets the verbosity of the MapReduce job, + * defaults to 'true' + * + * @param verbose + * The verbosity level. + */ + public void setVerbose( Boolean verbose ){ + _verbose = verbose; + } + + /** + * Gets the verbosity of the MapReduce job. + * + * @return the verbosity level. + */ + public Boolean isVerbose(){ + return _verbose; + } + + /** + * Get the name of the collection the MapReduce will read from + * + * @return name of the collection the MapReduce will read from + */ + public String getInput(){ + return _input; + } + + + /** + * Get the map function, as a JS String + * + * @return the map function (as a JS String) + */ + public String getMap(){ + return _map; + } + + /** + * Gets the reduce function, as a JS String + * + * @return the reduce function (as a JS String) + */ + public String getReduce(){ + return _reduce; + } + + /** + * Gets the output target (name of collection to save to) + * This value is nullable only if OutputType is set to INLINE + * + * @return The outputTarget + */ + public String getOutputTarget(){ + return _outputTarget; + } + + + /** + * Gets the OutputType for this instance. + * @return The outputType. + */ + public OutputType getOutputType(){ + return _outputType; + } + + + /** + * Gets the Finalize JS Function + * + * @return The finalize function (as a JS String). + */ + public String getFinalize(){ + return _finalize; + } + + /** + * Sets the Finalize JS Function + * + * @param finalize + * The finalize function (as a JS String) + */ + public void setFinalize( String finalize ){ + _finalize = finalize; + } + + /** + * Gets the query to run for this MapReduce job + * + * @return The query object + */ + public DBObject getQuery(){ + return _query; + } + + /** + * Gets the (optional) sort specification object + * + * @return the Sort DBObject + */ + public DBObject getSort(){ + return _sort; + } + + /** + * Sets the (optional) sort specification object + * + * @param sort + * The sort specification object + */ + public void setSort( DBObject sort ){ + _sort = sort; + } + + /** + * Gets the (optional) limit on input + * + * @return The limit specification object + */ + public int getLimit(){ + return _limit; + } + + /** + * Sets the (optional) limit on input + * + * @param limit + * The limit specification object + */ + public void setLimit( int limit ){ + _limit = limit; + } + + /** + * Gets the (optional) JavaScript scope + * + * @return The JavaScript scope + */ + public Map getScope(){ + return _scope; + } + + /** + * Sets the (optional) JavaScript scope + * + * @param scope + * The JavaScript scope + */ + public void setScope( Map scope ){ + _scope = scope; + } + + /** + * Sets the (optional) database name where the output collection should reside + * @param outputDB + */ + public void setOutputDB(String outputDB) { + this._outputDB = outputDB; + } + + + + public DBObject toDBObject() { + BasicDBObject cmd = new BasicDBObject(); + + cmd.put("mapreduce", _input); + cmd.put("map", _map); + cmd.put("reduce", _reduce); + cmd.put("verbose", _verbose); + + BasicDBObject out = new BasicDBObject(); + switch(_outputType) { + case INLINE: + out.put("inline", 1); + break; + case REPLACE: + out.put("replace", _outputTarget); + break; + case MERGE: + out.put("merge", _outputTarget); + break; + case REDUCE: + out.put("reduce", _outputTarget); + break; + } + if (_outputDB != null) + out.put("db", _outputDB); + cmd.put("out", out); + + if (_query != null) + cmd.put("query", _query); + + if (_finalize != null) + cmd.put( "finalize", _finalize ); + + if (_sort != null) + cmd.put("sort", _sort); + + if (_limit > 0) + cmd.put("limit", _limit); + + if (_scope != null) + cmd.put("scope", _scope); + + if (_extra != null) { + cmd.putAll(_extra); + } + + return cmd; + } + + public void addExtraOption(String name, Object value) { + if (_extra == null) + _extra = new BasicDBObject(); + _extra.put(name, value); + } + + public DBObject getExtraOptions() { + return _extra; + } + + /** + * Sets the read preference for this command. + * See the * documentation for {@link ReadPreference} + * for more information. + * + * @param preference Read Preference to use + */ + public void setReadPreference( ReadPreference preference ){ + _readPref = preference; + } + + /** + * Gets the read preference + * @return + */ + public ReadPreference getReadPreference(){ + return _readPref; + } + + + public String toString() { + return toDBObject().toString(); + } + + final String _input; + final String _map; + final String _reduce; + final String _outputTarget; + ReadPreference _readPref; + String _outputDB = null; + final OutputType _outputType; + final DBObject _query; + String _finalize; + DBObject _sort; + int _limit; + Map _scope; + Boolean _verbose = true; + DBObject _extra; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/MapReduceOutput.java b/src/com/massivecraft/mcore3/lib/mongodb/MapReduceOutput.java new file mode 100644 index 00000000..b5987291 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/MapReduceOutput.java @@ -0,0 +1,112 @@ +// MapReduceOutput.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * Represents the result of a map/reduce operation + * @author antoine + */ +public class MapReduceOutput { + + @SuppressWarnings("unchecked") + public MapReduceOutput( DBCollection from , DBObject cmd, CommandResult raw ){ + _commandResult = raw; + _cmd = cmd; + + if ( raw.containsField( "results" ) ) { + _coll = null; + _collname = null; + _resultSet = (Iterable) raw.get( "results" ); + } else { + Object res = raw.get("result"); + if (res instanceof String) { + _collname = (String) res; + } else { + BasicDBObject output = (BasicDBObject) res; + _collname = output.getString("collection"); + _dbname = output.getString("db"); + } + + DB db = from._db; + if (_dbname != null) { + db = db.getSisterDB(_dbname); + } + _coll = db.getCollection( _collname ); + // M/R only applies to master, make sure we dont go to slave for results + _coll.setOptions(_coll.getOptions() & ~Bytes.QUERYOPTION_SLAVEOK); + _resultSet = _coll.find(); + } + _counts = (BasicDBObject)raw.get( "counts" ); + } + + /** + * returns a cursor to the results of the operation + * @return + */ + public Iterable results(){ + return _resultSet; + } + + /** + * drops the collection that holds the results + */ + public void drop(){ + if ( _coll != null) + _coll.drop(); + } + + /** + * gets the collection that holds the results + * (Will return null if results are Inline) + * @return + */ + public DBCollection getOutputCollection(){ + return _coll; + } + + @Deprecated + public BasicDBObject getRaw(){ + return _commandResult; + } + + public CommandResult getCommandResult(){ + return _commandResult; + } + + public DBObject getCommand() { + return _cmd; + } + + public ServerAddress getServerUsed() { + return _commandResult.getServerUsed(); + } + + public String toString(){ + return _commandResult.toString(); + } + + final CommandResult _commandResult; + + final String _collname; + String _dbname = null; + final Iterable _resultSet; + final DBCollection _coll; + final BasicDBObject _counts; + final DBObject _cmd; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/Mongo.java b/src/com/massivecraft/mcore3/lib/mongodb/Mongo.java new file mode 100644 index 00000000..757b6cd9 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/Mongo.java @@ -0,0 +1,742 @@ +// Mongo.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.massivecraft.mcore3.lib.bson.io.PoolOutputBuffer; + +/** + * A database connection with internal pooling. + * For most application, you should have 1 Mongo instance for the entire JVM. + * + * The following are equivalent, and all connect to the + * local database running on the default port: + * + *

    + * Mongo mongo1 = new Mongo( "127.0.0.1" );
    + * Mongo mongo2 = new Mongo( "127.0.0.1", 27017 );
    + * Mongo mongo3 = new Mongo( new DBAddress( "127.0.0.1", 27017, "test" ) );
    + * Mongo mongo4 = new Mongo( new ServerAddress( "127.0.0.1") );
    + * 
    + * + * Mongo instances have connection pooling built in - see the requestStart + * and requestDone methods for more information. + * http://www.mongodb.org/display/DOCS/Java+Driver+Concurrency + * + *

    Connecting to a Replica Set

    + *

    + * You can connect to a + * replica set + * using the Java driver by passing several a list if ServerAddress to the + * Mongo constructor. + * For example: + *

    + *
    + * List addrs = new ArrayList();
    + * addrs.add( new ServerAddress( "127.0.0.1" , 27017 ) );
    + * addrs.add( new ServerAddress( "127.0.0.1" , 27018 ) );
    + * addrs.add( new ServerAddress( "127.0.0.1" , 27019 ) );
    + *
    + * Mongo mongo = new Mongo( addrs );
    + * 
    + * + *

    + * By default, all read and write operations will be made on the master. + * But it's possible to read from the slave(s) by using slaveOk: + *

    + *
    + * mongo.slaveOk();
    + * 
    + */ +public class Mongo { + + // Make sure you don't change the format of these two static variables. A preprocessing regexp + // is applied and updates the version based on configuration in build.properties. + + /** + * @deprecated Replaced by Mongo.getMajorVersion() + */ + @Deprecated + public static final int MAJOR_VERSION = 2; + + /** + * @deprecated Replaced by Mongo.getMinorVersion() + */ + @Deprecated + public static final int MINOR_VERSION = 8; + + private static final String FULL_VERSION = "2.8.0"; + + static int cleanerIntervalMS; + static { + cleanerIntervalMS = Integer.parseInt(System.getProperty("com.mongodb.cleanerIntervalMS", "1000")); + } + + /** + * Gets the major version of this library + * @return the major version, e.g. 2 + */ + public static int getMajorVersion() { + return MAJOR_VERSION; + } + + /** + * Gets the minor version of this library + * @return the minor version, e.g. 8 + */ + public static int getMinorVersion() { + return MINOR_VERSION; + } + + /** + * returns a database object + * @param addr the database address + * @return + */ + public static DB connect( DBAddress addr ){ + return new Mongo( addr ).getDB( addr.getDBName() ); + } + + /** + * Creates a Mongo instance based on a (single) mongodb node (localhost, default port) + * @throws UnknownHostException + * @throws MongoException + */ + public Mongo() + throws UnknownHostException , MongoException { + this( new ServerAddress() ); + } + + /** + * Creates a Mongo instance based on a (single) mongodb node (default port) + * @param host server to connect to + * @throws UnknownHostException if the database host cannot be resolved + * @throws MongoException + */ + public Mongo( String host ) + throws UnknownHostException , MongoException { + this( new ServerAddress( host ) ); + } + + /** + * Creates a Mongo instance based on a (single) mongodb node (default port) + * @param host server to connect to + * @param options default query options + * @throws UnknownHostException if the database host cannot be resolved + * @throws MongoException + */ + public Mongo( String host , MongoOptions options ) + throws UnknownHostException , MongoException { + this( new ServerAddress( host ) , options ); + } + + /** + * Creates a Mongo instance based on a (single) mongodb node + * @param host the database's host address + * @param port the port on which the database is running + * @throws UnknownHostException if the database host cannot be resolved + * @throws MongoException + */ + public Mongo( String host , int port ) + throws UnknownHostException , MongoException { + this( new ServerAddress( host , port ) ); + } + + /** + * Creates a Mongo instance based on a (single) mongodb node + * @see com.massivecraft.mcore3.lib.mongodb.ServerAddress + * @param addr the database address + * @throws MongoException + */ + public Mongo( ServerAddress addr ) + throws MongoException { + this( addr , new MongoOptions() ); + } + + /** + * Creates a Mongo instance based on a (single) mongo node using a given ServerAddress + * @see com.massivecraft.mcore3.lib.mongodb.ServerAddress + * @param addr the database address + * @param options default query options + * @throws MongoException + */ + public Mongo( ServerAddress addr , MongoOptions options ) + throws MongoException { + _addr = addr; + _addrs = null; + _options = options; + _applyMongoOptions(); + _connector = new DBTCPConnector( this , _addr ); + _connector.start(); + _cleaner = new DBCleanerThread(); + _cleaner.start(); + } + + /** + *

    Creates a Mongo in paired mode.
    This will also work for + * a replica set and will find all members (the master will be used by + * default).

    + * + * @see com.massivecraft.mcore3.lib.mongodb.ServerAddress + * @param left left side of the pair + * @param right right side of the pair + * @throws MongoException + */ + @Deprecated + public Mongo( ServerAddress left , ServerAddress right ) + throws MongoException { + this( left , right , new MongoOptions() ); + } + + /** + *

    Creates a Mongo connection in paired mode.
    This will also work for + * a replica set and will find all members (the master will be used by + * default).

    + * + * @see com.massivecraft.mcore3.lib.mongodb.ServerAddress + * @param left left side of the pair + * @param right right side of the pair + * @param options + * @throws MongoException + */ + @Deprecated + public Mongo( ServerAddress left , ServerAddress right , MongoOptions options ) + throws MongoException { + _addr = null; + _addrs = Arrays.asList( left , right ); + _options = options; + _applyMongoOptions(); + _connector = new DBTCPConnector( this , _addrs ); + _connector.start(); + + _cleaner = new DBCleanerThread(); + _cleaner.start(); + } + + /** + *

    Creates a Mongo based on a replica set, or pair. + * It will find all members (the master will be used by default). If you pass in a single server in the list, + * the driver will still function as if it is a replica set. If you have a standalone server, + * use the Mongo(ServerAddress) constructor.

    + * @see com.massivecraft.mcore3.lib.mongodb.ServerAddress + * @param replicaSetSeeds Put as many servers as you can in the list and + * the system will figure out the rest. + * @throws MongoException + */ + public Mongo( List replicaSetSeeds ) + throws MongoException { + this( replicaSetSeeds , new MongoOptions() ); + } + + /** + *

    Creates a Mongo based on a replica set, or pair. + * It will find all members (the master will be used by default).

    + * @see com.massivecraft.mcore3.lib.mongodb.ServerAddress + * @param replicaSetSeeds put as many servers as you can in the list. + * the system will figure the rest out + * @param options default query options + * @throws MongoException + */ + public Mongo( List replicaSetSeeds , MongoOptions options ) + throws MongoException { + + _addr = null; + _addrs = replicaSetSeeds; + _options = options; + _applyMongoOptions(); + _connector = new DBTCPConnector( this , _addrs ); + _connector.start(); + + _cleaner = new DBCleanerThread(); + _cleaner.start(); + } + + /** + * Creates a Mongo described by a URI. + * If only one address is used it will only connect to that node, otherwise it will discover all nodes. + * @param uri + * @see MongoURI + *

    examples: + *

  • mongodb://127.0.0.1
  • + *
  • mongodb://fred:foobar@127.0.0.1/
  • + *

    + * @throws MongoException + * @throws UnknownHostException + * @dochub connections + */ + + public Mongo( MongoURI uri ) + throws MongoException , UnknownHostException { + + _options = uri.getOptions(); + _applyMongoOptions(); + + if ( uri.getHosts().size() == 1 ){ + _addr = new ServerAddress( uri.getHosts().get(0) ); + _addrs = null; + _connector = new DBTCPConnector( this , _addr ); + } + else { + List replicaSetSeeds = new ArrayList( uri.getHosts().size() ); + for ( String host : uri.getHosts() ) + replicaSetSeeds.add( new ServerAddress( host ) ); + _addr = null; + _addrs = replicaSetSeeds; + _connector = new DBTCPConnector( this , replicaSetSeeds ); + } + + _connector.start(); + _cleaner = new DBCleanerThread(); + _cleaner.start(); + } + + /** + * gets a database object + * @param dbname the database name + * @return + */ + public DB getDB( String dbname ){ + + DB db = _dbs.get( dbname ); + if ( db != null ) + return db; + + db = new DBApiLayer( this , dbname , _connector ); + DB temp = _dbs.putIfAbsent( dbname , db ); + if ( temp != null ) + return temp; + return db; + } + + /** + * gets a collection of DBs used by the driver since this Mongo instance was created. + * This may include DBs that exist in the client but not yet on the server. + * @return + */ + public Collection getUsedDatabases(){ + return _dbs.values(); + } + + /** + * gets a list of all database names present on the server + * @return + * @throws MongoException + */ + @SuppressWarnings("rawtypes") + public List getDatabaseNames() + throws MongoException { + + BasicDBObject cmd = new BasicDBObject(); + cmd.put("listDatabases", 1); + + + CommandResult res = getDB( "admin" ).command(cmd, getOptions()); + res.throwOnError(); + + List l = (List)res.get("databases"); + + List list = new ArrayList(); + + for (Object o : l) { + list.add(((BasicDBObject)o).getString("name")); + } + return list; + } + + + /** + * Drops the database if it exists. + * @param dbName name of database to drop + * @throws MongoException + */ + public void dropDatabase(String dbName) + throws MongoException { + + getDB( dbName ).dropDatabase(); + } + + /** + * gets this driver version + * @return the full version string of this driver, e.g. "2.8.0" + */ + public String getVersion(){ + return FULL_VERSION; + } + + /** + * returns a string representing the hosts used in this Mongo instance + * @return + */ + public String debugString(){ + return _connector.debugString(); + } + + /** + * Gets the current master's hostname + * @return + */ + public String getConnectPoint(){ + return _connector.getConnectPoint(); + } + + /** + * Gets the underlying TCP connector + * @return + */ + public DBTCPConnector getConnector() { + return _connector; + } + + /** + * Gets the replica set status object + * @return + */ + public ReplicaSetStatus getReplicaSetStatus() { + return _connector.getReplicaSetStatus(); + } + + /** + * Gets the address of the current master + * @return the address + */ + public ServerAddress getAddress(){ + return _connector.getAddress(); + } + + /** + * Gets a list of all server addresses used when this Mongo was created + * @return + */ + public List getAllAddress() { + List result = _connector.getAllAddress(); + if (result == null) { + return Arrays.asList(getAddress()); + } + return result; + } + + /** + * Gets the list of server addresses currently seen by the connector. + * This includes addresses auto-discovered from a replica set. + * @return + */ + public List getServerAddressList() { + return _connector.getServerAddressList(); + } + + /** + * closes the underlying connector, which in turn closes all open connections. + * Once called, this Mongo instance can no longer be used. + */ + public void close(){ + + try { + _connector.close(); + } catch (final Throwable t) { /* nada */ } + + _cleaner.interrupt(); + + try { + _cleaner.join(); + } catch (InterruptedException e) { + //end early + } + } + + /** + * Sets the write concern for this database. Will be used as default for + * writes to any collection in any database. See the + * documentation for {@link WriteConcern} for more information. + * + * @param concern write concern to use + */ + public void setWriteConcern( WriteConcern concern ){ + _concern = concern; + } + + /** + * Gets the default write concern + * @return + */ + public WriteConcern getWriteConcern(){ + return _concern; + } + + /** + * Sets the read preference for this database. Will be used as default for + * reads from any collection in any database. See the + * documentation for {@link ReadPreference} for more information. + * + * @param preference Read Preference to use + */ + public void setReadPreference( ReadPreference preference ){ + _readPref = preference; + } + + /** + * Gets the default read preference + * @return + */ + public ReadPreference getReadPreference(){ + return _readPref; + } + + /** + * makes it possible to run read queries on slave nodes + * + * @deprecated Replaced with ReadPreference.SECONDARY + * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY + */ + @Deprecated + public void slaveOk(){ + addOption( Bytes.QUERYOPTION_SLAVEOK ); + } + + /** + * adds a default query option + * @param option + */ + public void addOption( int option ){ + _netOptions.add( option ); + } + + /** + * sets the default query options + * @param options + */ + public void setOptions( int options ){ + _netOptions.set( options ); + } + + /** + * reset the default query options + */ + public void resetOptions(){ + _netOptions.reset(); + } + + /** + * gets the default query options + * @return + */ + public int getOptions(){ + return _netOptions.get(); + } + + /** + * Helper method for setting up MongoOptions at instantiation + * so that any options which affect this connection can be set. + */ + @SuppressWarnings("deprecation") + void _applyMongoOptions() { + if (_options.slaveOk) slaveOk(); + setWriteConcern( _options.getWriteConcern() ); + } + + /** + * Returns the mongo options. + */ + public MongoOptions getMongoOptions() { + return _options; + } + + /** + * Gets the maximum size for a BSON object supported by the current master server. + * Note that this value may change over time depending on which server is master. + * If the size is not known yet, a request may be sent to the master server + * @return the maximum size + */ + public int getMaxBsonObjectSize() { + int maxsize = _connector.getMaxBsonObjectSize(); + if (maxsize == 0) + maxsize = _connector.fetchMaxBsonObjectSize(); + return maxsize > 0 ? maxsize : Bytes.MAX_OBJECT_SIZE; + } + + final ServerAddress _addr; + final List _addrs; + final MongoOptions _options; + final DBTCPConnector _connector; + final ConcurrentMap _dbs = new ConcurrentHashMap(); + private WriteConcern _concern = WriteConcern.NORMAL; + private ReadPreference _readPref = ReadPreference.PRIMARY; + final Bytes.OptionHolder _netOptions = new Bytes.OptionHolder( null ); + final DBCleanerThread _cleaner; + + com.massivecraft.mcore3.lib.bson.util.SimplePool _bufferPool = + new com.massivecraft.mcore3.lib.bson.util.SimplePool( 1000 ){ + + protected PoolOutputBuffer createNew(){ + return new PoolOutputBuffer(); + } + + }; + + /** + * Forces the master server to fsync the RAM data to disk + * This is done automatically by the server at intervals, but can be forced for better reliability. + * @param async if true, the fsync will be done asynchronously on the server. + * @return + */ + public CommandResult fsync(boolean async) { + DBObject cmd = new BasicDBObject("fsync", 1); + if (async) { + cmd.put("async", 1); + } + return getDB("admin").command(cmd); + } + + /** + * Forces the master server to fsync the RAM data to disk, then lock all writes. + * The database will be read-only after this command returns. + * @return + */ + public CommandResult fsyncAndLock() { + DBObject cmd = new BasicDBObject("fsync", 1); + cmd.put("lock", 1); + return getDB("admin").command(cmd); + } + + /** + * Unlocks the database, allowing the write operations to go through. + * This command may be asynchronous on the server, which means there may be a small delay before the database becomes writable. + * @return + */ + public DBObject unlock() { + DB db = getDB("admin"); + DBCollection col = db.getCollection("$cmd.sys.unlock"); + return col.findOne(); + } + + /** + * Returns true if the database is locked (read-only), false otherwise. + * @return + */ + public boolean isLocked() { + DB db = getDB("admin"); + DBCollection col = db.getCollection("$cmd.sys.inprog"); + BasicDBObject res = (BasicDBObject) col.findOne(); + if (res.containsField("fsyncLock")) { + return res.getInt("fsyncLock") == 1; + } + return false; + } + + // ------- + + + /** + * Mongo.Holder can be used as a static place to hold several instances of Mongo. + * Security is not enforced at this level, and needs to be done on the application side. + */ + public static class Holder { + + /** + * Attempts to find an existing Mongo instance matching that URI in the holder, and returns it if exists. + * Otherwise creates a new Mongo instance based on this URI and adds it to the holder. + * @param uri the Mongo URI + * @return + * @throws MongoException + * @throws UnknownHostException + */ + public Mongo connect( MongoURI uri ) + throws MongoException , UnknownHostException { + + String key = _toKey( uri ); + + Mongo m = _mongos.get(key); + if ( m != null ) + return m; + + m = new Mongo( uri ); + + Mongo temp = _mongos.putIfAbsent( key , m ); + if ( temp == null ){ + // ours got in + return m; + } + + // there was a race and we lost + // close ours and return the other one + m.close(); + return temp; + } + + String _toKey( MongoURI uri ){ + StringBuilder buf = new StringBuilder(); + for ( String h : uri.getHosts() ) + buf.append( h ).append( "," ); + buf.append( uri.getOptions() ); + buf.append( uri.getUsername() ); + return buf.toString(); + } + + public static Holder singleton() { return _default; } + + private static Holder _default = new Holder(); + private final ConcurrentMap _mongos = new ConcurrentHashMap(); + + } + + class DBCleanerThread extends Thread { + + DBCleanerThread() { + setDaemon(true); + setName("MongoCleaner" + hashCode()); + } + + public void run() { + while (_connector.isOpen()) { + try { + try { + Thread.sleep(cleanerIntervalMS); + } catch (InterruptedException e) { + //caused by the Mongo instance being closed -- proceed with cleanup + } + for (DB db : _dbs.values()) { + db.cleanCursors(true); + } + } catch (Throwable t) { + // thread must never die + } + } + } + } + + @Override + public String toString() { + StringBuilder str = new StringBuilder("Mongo: "); + List list = getServerAddressList(); + if (list == null || list.size() == 0) + str.append("null"); + else { + for ( ServerAddress addr : list ) + str.append( addr.toString() ).append( ',' ); + str.deleteCharAt( str.length() - 1 ); + } + return str.toString(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/MongoException.java b/src/com/massivecraft/mcore3/lib/mongodb/MongoException.java new file mode 100644 index 00000000..0ddcf15b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/MongoException.java @@ -0,0 +1,171 @@ +// MongoException.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.BSONObject; + +/** + * A general exception raised in Mongo + * @author antoine + */ +public class MongoException extends RuntimeException { + + private static final long serialVersionUID = -4415279469780082174L; + + /** + * @param msg the message + */ + public MongoException( String msg ){ + super( msg ); + _code = -3; + } + + /** + * + * @param code the error code + * @param msg the message + */ + public MongoException( int code , String msg ){ + super( msg ); + _code = code; + } + + /** + * + * @param msg the message + * @param t the throwable cause + */ + public MongoException( String msg , Throwable t ){ + super( msg , _massage( t ) ); + _code = -4; + } + + /** + * + * @param code the error code + * @param msg the message + * @param t the throwable cause + */ + public MongoException( int code , String msg , Throwable t ){ + super( msg , _massage( t ) ); + _code = code; + } + + /** + * Creates a MongoException from a BSON object representing an error + * @param o + */ + public MongoException( BSONObject o ){ + this( ServerError.getCode( o ) , ServerError.getMsg( o , "UNKNOWN" ) ); + } + + static MongoException parse( BSONObject o ){ + String s = ServerError.getMsg( o , null ); + if ( s == null ) + return null; + return new MongoException( ServerError.getCode( o ) , s ); + } + + + static Throwable _massage( Throwable t ){ + if ( t instanceof Network ) + return ((Network)t)._ioe; + return t; + } + + /** + * Subclass of MongoException representing a network-related exception + */ + public static class Network extends MongoException { + + private static final long serialVersionUID = -4415279469780082174L; + + Network( String msg , java.io.IOException ioe ){ + super( -2 , msg , ioe ); + _ioe = ioe; + } + + Network( java.io.IOException ioe ){ + super( ioe.toString() , ioe ); + _ioe = ioe; + } + + final java.io.IOException _ioe; + } + + /** + * Subclass of MongoException representing a duplicate key exception + */ + public static class DuplicateKey extends MongoException { + + private static final long serialVersionUID = -4415279469780082174L; + + DuplicateKey( int code , String msg ){ + super( code , msg ); + } + } + + /** + * Subclass of MongoException representing a cursor-not-found exception + */ + public static class CursorNotFound extends MongoException { + + private static final long serialVersionUID = -4415279469780082174L; + + private final long cursorId; + private final ServerAddress serverAddress; + + /** + * + * @param cursorId + * @param serverAddress + */ + CursorNotFound(long cursorId, ServerAddress serverAddress){ + super( -5 , "cursor " + cursorId + " not found on server " + serverAddress ); + this.cursorId = cursorId; + this.serverAddress = serverAddress; + } + + /** + * Get the cursor id that wasn't found. + * @return + */ + public long getCursorId() { + return cursorId; + } + + /** + * The server address where the cursor is. + * @return + */ + public ServerAddress getServerAddress() { + return serverAddress; + } + } + + /** + * Gets the exception code + * @return + */ + public int getCode(){ + return _code; + } + + final int _code; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/MongoInternalException.java b/src/com/massivecraft/mcore3/lib/mongodb/MongoInternalException.java new file mode 100644 index 00000000..d8ecff6c --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/MongoInternalException.java @@ -0,0 +1,47 @@ +// MongoInternalException.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * An Mongo exception internal to the driver, not carrying any error code + * @author antoine + */ +public class MongoInternalException extends MongoException { + + private static final long serialVersionUID = -4415279469780082174L; + + /** + * + * @param msg the message + */ + public MongoInternalException( String msg ){ + super( msg ); + } + + /** + * + * @param msg the message + * @param t the throwable cause + */ + public MongoInternalException( String msg , Throwable t ){ + super( msg , MongoException._massage( t ) ); + } + +} + diff --git a/src/com/massivecraft/mcore3/lib/mongodb/MongoOptions.java b/src/com/massivecraft/mcore3/lib/mongodb/MongoOptions.java new file mode 100644 index 00000000..ede647f6 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/MongoOptions.java @@ -0,0 +1,515 @@ +// MongoOptions.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import javax.net.SocketFactory; + +/** + * Various settings for the driver. + * Not thread safe. + */ +public class MongoOptions { + + public MongoOptions(){ + reset(); + } + + public void reset(){ + connectionsPerHost = Bytes.CONNECTIONS_PER_HOST; + threadsAllowedToBlockForConnectionMultiplier = 5; + maxWaitTime = 1000 * 60 * 2; + connectTimeout = 0; + socketTimeout = 0; + socketKeepAlive = false; + autoConnectRetry = false; + maxAutoConnectRetryTime = 0; + slaveOk = false; + safe = false; + w = 0; + wtimeout = 0; + fsync = false; + j = false; + dbDecoderFactory = DefaultDBDecoder.FACTORY; + dbEncoderFactory = DefaultDBEncoder.FACTORY; + socketFactory = SocketFactory.getDefault(); + description = null; + } + + public MongoOptions copy() { + MongoOptions m = new MongoOptions(); + m.connectionsPerHost = connectionsPerHost; + m.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier; + m.maxWaitTime = maxWaitTime; + m.connectTimeout = connectTimeout; + m.socketTimeout = socketTimeout; + m.socketKeepAlive = socketKeepAlive; + m.autoConnectRetry = autoConnectRetry; + m.maxAutoConnectRetryTime = maxAutoConnectRetryTime; + m.slaveOk = slaveOk; + m.safe = safe; + m.w = w; + m.wtimeout = wtimeout; + m.fsync = fsync; + m.j = j; + m.dbDecoderFactory = dbDecoderFactory; + m.dbEncoderFactory = dbEncoderFactory; + m.socketFactory = socketFactory; + m.description = description; + return m; + } + + /** + * Helper method to return the appropriate WriteConcern instance based + * on the current related options settings. + **/ + public WriteConcern getWriteConcern(){ + // Ensure we only set writeconcern once; if non-default w, etc skip safe (implied) + if ( w != 0 || wtimeout != 0 || fsync ) + return new WriteConcern( w , wtimeout , fsync ); + else if (safe) + return WriteConcern.SAFE; + else + return WriteConcern.NORMAL; + } + + /** + *

    The description for Mongo instances created with these options. This is used in various places like logging.

    + */ + public String description; + + /** + * The maximum number of connections allowed per host for this Mongo instance. + * Those connections will be kept in a pool when idle. + * Once the pool is exhausted, any operation requiring a connection will block waiting for an available connection. + * Default is 10. + * @see {@linkplain MongoOptions#threadsAllowedToBlockForConnectionMultiplier}

    + */ + public int connectionsPerHost; + + /** + * this multiplier, multiplied with the connectionsPerHost setting, gives the maximum number of threads that + * may be waiting for a connection to become available from the pool. + * All further threads will get an exception right away. + * For example if connectionsPerHost is 10 and threadsAllowedToBlockForConnectionMultiplier is 5, then up to 50 threads can wait for a connection. + * Default is 5. + */ + public int threadsAllowedToBlockForConnectionMultiplier; + + /** + * The maximum wait time in milliseconds that a thread may wait for a connection to become available. + * Default is 120,000. A value of 0 means that it will not wait. A negative value means to wait indefinitely. + */ + public int maxWaitTime; + + /** + * The connection timeout in milliseconds. + * It is used solely when establishing a new connection {@link java.net.Socket#connect(java.net.SocketAddress, int) } + * Default is 0 and means no timeout. + */ + public int connectTimeout; + + /** + * The socket timeout in milliseconds + * It is used for I/O socket read and write operations {@link java.net.Socket#setSoTimeout(int)} + * Default is 0 and means no timeout. + */ + public int socketTimeout; + + /** + * This flag controls the socket keep alive feature that keeps a connection alive through firewalls {@link java.net.Socket#setKeepAlive(boolean)} + * Default is false. + */ + public boolean socketKeepAlive; + + /** + * If true, the driver will keep trying to connect to the same server in case that the socket cannot be established. + * There is maximum amount of time to keep retrying, which is 15s by default. + * This can be useful to avoid some exceptions being thrown when a server is down temporarily by blocking the operations. + * It also can be useful to smooth the transition to a new master (so that a new master is elected within the retry time). + * Note that when using this flag: + * - for a replica set, the driver will trying to connect to the old master for that time, instead of failing over to the new one right away + * - this does not prevent exception from being thrown in read/write operations on the socket, which must be handled by application + * + * Even if this flag is false, the driver already has mechanisms to automatically recreate broken connections and retry the read operations. + * Default is false. + */ + public boolean autoConnectRetry; + + /** + * The maximum amount of time in MS to spend retrying to open connection to the same server. + * Default is 0, which means to use the default 15s if autoConnectRetry is on. + */ + public long maxAutoConnectRetryTime; + + /** + * This flag specifies if the driver is allowed to read from secondary (slave) servers. + * Specifically in the current implementation, the driver will avoid reading from the primary server and round robin requests to secondaries. + * Driver also factors in the latency to secondaries when choosing a server. + * Note that reading from secondaries can increase performance and reliability, but it may result in temporary inconsistent results. + * Default is false. + * + * @deprecated Replaced in MongoDB 2.0/Java Driver 2.7 with ReadPreference.SECONDARY + * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY + */ + @Deprecated + public boolean slaveOk; + + /** + * Override the DBCallback factory. Default is for the standard Mongo Java driver configuration. + */ + public DBDecoderFactory dbDecoderFactory; + + /** + * Override the encoding factory. Default is for the standard Mongo Java driver configuration. + */ + public DBEncoderFactory dbEncoderFactory; + + /** + * If true the driver will use a WriteConcern of WriteConcern.SAFE for all operations. + * If w, wtimeout, fsync or j are specified, this setting is ignored. + * Default is false. + */ + public boolean safe; + + /** + * The "w" value, (number of writes), of the global WriteConcern. + * Default is 0. + */ + public int w; + + /** + * The "wtimeout" value of the global WriteConcern. + * Default is 0. + */ + public int wtimeout; + + /** + * The "fsync" value of the global WriteConcern. + * true indicates writes should wait for data to be written to server data file + * Default is false. + */ + public boolean fsync; + + /** + * The "j" value of the global WriteConcern. + * true indicates writes should wait for a journaling group commit + * Default is false. + */ + public boolean j; + + /** + * sets the socket factory for creating sockets to mongod + * Default is SocketFactory.getDefault() + */ + public SocketFactory socketFactory; + + public String toString(){ + StringBuilder buf = new StringBuilder(); + buf.append( "description=" ).append( description ).append( ", " ); + buf.append( "connectionsPerHost=" ).append( connectionsPerHost ).append( ", " ); + buf.append( "threadsAllowedToBlockForConnectionMultiplier=" ).append( threadsAllowedToBlockForConnectionMultiplier ).append( ", " ); + buf.append( "maxWaitTime=" ).append( maxWaitTime ).append( ", " ); + buf.append( "connectTimeout=" ).append( connectTimeout ).append( ", " ); + buf.append( "socketTimeout=" ).append( socketTimeout ).append( ", " ); + buf.append( "socketKeepAlive=" ).append( socketKeepAlive ).append( ", " ); + buf.append( "autoConnectRetry=" ).append( autoConnectRetry ).append( ", " ); + buf.append( "maxAutoConnectRetryTime=" ).append( maxAutoConnectRetryTime ).append( ", " ); + buf.append( "slaveOk=" ).append( slaveOk ).append( ", " ); + buf.append( "safe=" ).append( safe ).append( ", " ); + buf.append( "w=" ).append( w ).append( ", " ); + buf.append( "wtimeout=" ).append( wtimeout ).append( ", " ); + buf.append( "fsync=" ).append( fsync ).append( ", " ); + buf.append( "j=" ).append( j ); + + return buf.toString(); + } + + /** + * @return The description for Mongo instances created with these options + */ + public synchronized String getDescription() { + return description; + } + + /** + * + * @param desc The description for Mongo instances created with these options + */ + public synchronized void setDescription(String desc) { + description = desc; + } + + /** + * + * @return the maximum number of connections allowed per host for this Mongo instance + */ + public synchronized int getConnectionsPerHost() { + return connectionsPerHost; + } + + /** + * + * @param connections sets the maximum number of connections allowed per host for this Mongo instance + */ + public synchronized void setConnectionsPerHost(int connections) { + connectionsPerHost = connections; + } + + /** + * + * @return the maximum number of threads that + * may be waiting for a connection + */ + public synchronized int getThreadsAllowedToBlockForConnectionMultiplier() { + return threadsAllowedToBlockForConnectionMultiplier; + } + + /** + * + * @param this multiplied with connectionsPerHost, sets the maximum number of threads that + * may be waiting for a connection + */ + public synchronized void setThreadsAllowedToBlockForConnectionMultiplier(int threads) { + threadsAllowedToBlockForConnectionMultiplier = threads; + } + + /** + * + * @return The maximum time in milliseconds that threads wait for a connection + */ + public synchronized int getMaxWaitTime() { + return maxWaitTime; + } + + /** + * + * @param timeMS set the maximum time in milliseconds that threads wait for a connection + */ + public synchronized void setMaxWaitTime(int timeMS) { + maxWaitTime = timeMS; + } + + /** + * + * @return the connection timeout in milliseconds. + */ + public synchronized int getConnectTimeout() { + return connectTimeout; + } + + /** + * + * @param timeoutMS set the connection timeout in milliseconds. + */ + public synchronized void setConnectTimeout(int timeoutMS) { + connectTimeout = timeoutMS; + } + + /** + * + * @return The socket timeout in milliseconds + */ + public synchronized int getSocketTimeout() { + return socketTimeout; + } + + /** + * + * @param timeoutMS set the socket timeout in milliseconds + */ + public synchronized void setSocketTimeout(int timeoutMS) { + socketTimeout = timeoutMS; + } + + /** + * + * @return connection keep-alive flag + */ + public synchronized boolean isSocketKeepAlive() { + return socketKeepAlive; + } + + /** + * + * @param keepAlive set connection keep-alive flag + */ + public synchronized void setSocketKeepAlive(boolean keepAlive) { + socketKeepAlive = keepAlive; + } + + /** + * + * @return keep trying connection flag + */ + public synchronized boolean isAutoConnectRetry() { + return autoConnectRetry; + } + + /** + * + * @param retry sets keep trying connection flag + */ + public synchronized void setAutoConnectRetry(boolean retry) { + autoConnectRetry = retry; + } + + /** + * + * @return max time in MS to retrying open connection + */ + public synchronized long getMaxAutoConnectRetryTime() { + return maxAutoConnectRetryTime; + } + + /** + * + * @param retryTimeMS set max time in MS to retrying open connection + */ + public synchronized void setMaxAutoConnectRetryTime(long retryTimeMS) { + maxAutoConnectRetryTime = retryTimeMS; + } + + /** + * + * @return the DBCallback decoding factory + */ + public synchronized DBDecoderFactory getDbDecoderFactory() { + return dbDecoderFactory; + } + + /** + * + * @param factory sets the DBCallback decoding factory + */ + public synchronized void setDbDecoderFactory(DBDecoderFactory factory) { + dbDecoderFactory = factory; + } + + /** + * + * @return the encoding factory + */ + public synchronized DBEncoderFactory getDbEncoderFactory() { + return dbEncoderFactory; + } + + /** + * + * @param factory sets the encoding factory + */ + public synchronized void setDbEncoderFactory(DBEncoderFactory factory) { + dbEncoderFactory = factory; + } + + /** + * + * @return true if driver uses WriteConcern.SAFE for all operations. + */ + public synchronized boolean isSafe() { + return safe; + } + + /** + * + * @param isSafe true if driver uses WriteConcern.SAFE for all operations. + */ + public synchronized void setSafe(boolean isSafe) { + safe = isSafe; + } + + /** + * + * @return value returns the number of writes of the global WriteConcern. + */ + public synchronized int getW() { + return w; + } + + /** + * + * @param val set the number of writes of the global WriteConcern. + */ + public synchronized void setW(int val) { + w = val; + } + + /** + * + * @return timeout for write operation + */ + public synchronized int getWtimeout() { + return wtimeout; + } + + /** + * + * @param timeoutMS sets timeout for write operation + */ + public synchronized void setWtimeout(int timeoutMS) { + wtimeout = timeoutMS; + } + + /** + * + * @return true if global write concern is set to fsync + */ + public synchronized boolean isFsync() { + return fsync; + } + + /** + * + * @param sync sets global write concern's fsync safe value + */ + public synchronized void setFsync(boolean sync) { + fsync = sync; + } + + /** + * + * @return true if global write concern is set to journal safe + */ + public synchronized boolean isJ() { + return j; + } + + /** + * + * @param safe sets global write concern's journal safe value + */ + public synchronized void setJ(boolean safe) { + j = safe; + } + + /** + * + * @return the socket factory for creating sockets to mongod + */ + public synchronized SocketFactory getSocketFactory() { + return socketFactory; + } + + /** + * + * @param factory sets the socket factory for creating sockets to mongod + */ + public synchronized void setSocketFactory(SocketFactory factory) { + socketFactory = factory; + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/MongoURI.java b/src/com/massivecraft/mcore3/lib/mongodb/MongoURI.java new file mode 100644 index 00000000..85f0b066 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/MongoURI.java @@ -0,0 +1,290 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +/** + * Represents a URI + * which can be used to create a Mongo instance. The URI describes the hosts to + * be used and options. + * + * The Java driver supports the following options (case insensitive):
    + * + *
      + *
    • maxpoolsize
    • + *
    • waitqueuemultiple
    • + *
    • waitqueuetimeoutms
    • + *
    • connecttimeoutms
    • + *
    • sockettimeoutms
    • + *
    • autoconnectretry
    • + *
    • slaveok
    • + *
    • safe
    • + *
    • w
    • + *
    • wtimeout
    • + *
    • fsync
    • + *
    + */ +public class MongoURI { + + public static final String MONGODB_PREFIX = "mongodb://"; + + /** + * Creates a MongoURI described by a String. + * examples + * mongodb://127.0.0.1 + * mongodb://fred:foobar@127.0.0.1/ + * @param uri the URI + * @dochub connections + */ + public MongoURI( String uri ){ + _uri = uri; + if ( ! uri.startsWith( MONGODB_PREFIX ) ) + throw new IllegalArgumentException( "uri needs to start with " + MONGODB_PREFIX ); + + uri = uri.substring(MONGODB_PREFIX.length()); + + String serverPart; + String nsPart; + String optionsPart; + + { + int idx = uri.lastIndexOf( "/" ); + if ( idx < 0 ){ + serverPart = uri; + nsPart = null; + optionsPart = null; + } + else { + serverPart = uri.substring( 0 , idx ); + nsPart = uri.substring( idx + 1 ); + + idx = nsPart.indexOf( "?" ); + if ( idx >= 0 ){ + optionsPart = nsPart.substring( idx + 1 ); + nsPart = nsPart.substring( 0 , idx ); + } + else { + optionsPart = null; + } + + } + } + + { // _username,_password,_hosts + List all = new LinkedList(); + + + int idx = serverPart.indexOf( "@" ); + + if ( idx > 0 ){ + String authPart = serverPart.substring( 0 , idx ); + serverPart = serverPart.substring( idx + 1 ); + + idx = authPart.indexOf( ":" ); + _username = authPart.substring( 0, idx ); + _password = authPart.substring( idx + 1 ).toCharArray(); + } + else { + _username = null; + _password = null; + } + + for ( String s : serverPart.split( "," ) ) + all.add( s ); + + _hosts = Collections.unmodifiableList( all ); + } + + if ( nsPart != null ){ // _database,_collection + int idx = nsPart.indexOf( "." ); + if ( idx < 0 ){ + _database = nsPart; + _collection = null; + } + else { + _database = nsPart.substring( 0 , idx ); + _collection = nsPart.substring( idx + 1 ); + } + } + else { + _database = null; + _collection = null; + } + + if ( optionsPart != null && optionsPart.length() > 0 ) parseOptions( optionsPart ); + } + + @SuppressWarnings("deprecation") + private void parseOptions( String optionsPart ){ + for ( String _part : optionsPart.split( "&|;" ) ){ + int idx = _part.indexOf( "=" ); + if ( idx >= 0 ){ + String key = _part.substring( 0, idx ).toLowerCase(); + String value = _part.substring( idx + 1 ); + if ( key.equals( "maxpoolsize" ) ) _options.connectionsPerHost = Integer.parseInt( value ); + else if ( key.equals( "minpoolsize" ) ) + LOGGER.warning( "Currently No support in Java driver for Min Pool Size." ); + else if ( key.equals( "waitqueuemultiple" ) ) + _options.threadsAllowedToBlockForConnectionMultiplier = Integer.parseInt( value ); + else if ( key.equals( "waitqueuetimeoutms" ) ) _options.maxWaitTime = Integer.parseInt( value ); + else if ( key.equals( "connecttimeoutms" ) ) _options.connectTimeout = Integer.parseInt( value ); + else if ( key.equals( "sockettimeoutms" ) ) _options.socketTimeout = Integer.parseInt( value ); + else if ( key.equals( "autoconnectretry" ) ) _options.autoConnectRetry = _parseBoolean( value ); + else if ( key.equals( "slaveok" ) ) _options.slaveOk = _parseBoolean( value ); + else if ( key.equals( "safe" ) ) _options.safe = _parseBoolean( value ); + else if ( key.equals( "w" ) ) _options.w = Integer.parseInt( value ); + else if ( key.equals( "wtimeout" ) ) _options.wtimeout = Integer.parseInt( value ); + else if ( key.equals( "fsync" ) ) _options.fsync = _parseBoolean( value ); + else LOGGER.warning( "Unknown or Unsupported Option '" + value + "'" ); + } + } + } + + boolean _parseBoolean( String _in ){ + String in = _in.trim(); + if ( in != null && in.length() > 0 && ( in.equals( "1" ) || in.toLowerCase().equals( "true" ) || in.toLowerCase() + .equals( "yes" ) ) ) + return true; + else return false; + } + + // --------------------------------- + + /** + * Gets the username + * @return + */ + public String getUsername(){ + return _username; + } + + /** + * Gets the password + * @return + */ + public char[] getPassword(){ + return _password; + } + + /** + * Gets the list of hosts + * @return + */ + public List getHosts(){ + return _hosts; + } + + /** + * Gets the database name + * @return + */ + public String getDatabase(){ + return _database; + } + + /** + * Gets the collection name + * @return + */ + public String getCollection(){ + return _collection; + } + + /** + * Gets the options + * @return + */ + public MongoOptions getOptions(){ + return _options; + } + + /** + * creates a Mongo instance based on the URI + * @return + * @throws MongoException + * @throws UnknownHostException + */ + public Mongo connect() + throws MongoException , UnknownHostException { + // TODO caching? + return new Mongo( this ); + } + + /** + * returns the DB object from a newly created Mongo instance based on this URI + * @return + * @throws MongoException + * @throws UnknownHostException + */ + public DB connectDB() + throws MongoException , UnknownHostException { + // TODO auth + return connect().getDB( _database ); + } + + /** + * returns the URI's DB object from a given Mongo instance + * @param m + * @return + */ + public DB connectDB( Mongo m ){ + // TODO auth + return m.getDB( _database ); + } + + /** + * returns the URI's Collection from a given DB object + * @param db + * @return + */ + public DBCollection connectCollection( DB db ){ + return db.getCollection( _collection ); + } + + /** + * returns the URI's Collection from a given Mongo instance + * @param m + * @return + */ + public DBCollection connectCollection( Mongo m ){ + return connectDB( m ).getCollection( _collection ); + } + + // --------------------------------- + + final String _username; + final char[] _password; + final List _hosts; + final String _database; + final String _collection; + + final MongoOptions _options = new MongoOptions(); + + final String _uri; + + static final Logger LOGGER = Logger.getLogger( "com.mongodb.MongoURI" ); + + @Override + public String toString() { + return _uri; + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/OutMessage.java b/src/com/massivecraft/mcore3/lib/mongodb/OutMessage.java new file mode 100644 index 00000000..f0ca5378 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/OutMessage.java @@ -0,0 +1,166 @@ +// OutMessage.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicInteger; + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.BasicBSONEncoder; +import com.massivecraft.mcore3.lib.bson.io.PoolOutputBuffer; + +class OutMessage extends BasicBSONEncoder { + + static AtomicInteger ID = new AtomicInteger(1); + + static OutMessage query( Mongo m , int options , String ns , int numToSkip , int batchSize , DBObject query , DBObject fields ){ + return query( m, options, ns, numToSkip, batchSize, query, fields, ReadPreference.PRIMARY ); + } + + static OutMessage query( Mongo m , int options , String ns , int numToSkip , int batchSize , DBObject query , DBObject fields, ReadPreference readPref ){ + return query( m, options, ns, numToSkip, batchSize, query, fields, readPref, DefaultDBEncoder.FACTORY.create()); + } + + static OutMessage query( Mongo m , int options , String ns , int numToSkip , int batchSize , DBObject query , DBObject fields, ReadPreference readPref, DBEncoder enc ){ + OutMessage out = new OutMessage( m , 2004, enc ); + + out._appendQuery( options , ns , numToSkip , batchSize , query , fields, readPref); + + return out; + } + + OutMessage( Mongo m ){ + this( m , DefaultDBEncoder.FACTORY.create() ); + } + + OutMessage( Mongo m , int op ){ + this( m ); + reset( op ); + } + + OutMessage( Mongo m , DBEncoder encoder ) { + _encoder = encoder; + _mongo = m; + _buffer = _mongo == null ? new PoolOutputBuffer() : _mongo._bufferPool.get(); + _buffer.reset(); + + set( _buffer ); + } + + OutMessage( Mongo m , int op , DBEncoder enc ) { + this( m , enc ); + reset( op ); + } + private void _appendQuery( int options , String ns , int numToSkip , int batchSize , DBObject query , DBObject fields, ReadPreference readPref){ + _queryOptions = options; + _readPref = readPref; + + //If the readPrefs are non-null and non-primary, set slaveOk query option + if (_readPref != null && !(_readPref instanceof ReadPreference.PrimaryReadPreference)) + _queryOptions |= Bytes.QUERYOPTION_SLAVEOK; + + writeInt( _queryOptions ); + writeCString( ns ); + + writeInt( numToSkip ); + writeInt( batchSize ); + + putObject( query ); + if ( fields != null ) + putObject( fields ); + + } + + private void reset( int op ){ + done(); + _buffer.reset(); + set( _buffer ); + + _id = ID.getAndIncrement(); + + writeInt( 0 ); // length: will set this later + writeInt( _id ); + writeInt( 0 ); // response to + writeInt( op ); + } + + void prepare(){ + _buffer.writeInt( 0 , _buffer.size() ); + } + + + void pipe( OutputStream out ) + throws IOException { + _buffer.pipe( out ); + } + + int size(){ + return _buffer.size(); + } + + byte[] toByteArray(){ + return _buffer.toByteArray(); + } + + void doneWithMessage(){ + if ( _buffer != null && _mongo != null ) { + _buffer.reset(); + _mongo._bufferPool.done( _buffer ); + } + + _buffer = null; + _mongo = null; + } + + boolean hasOption( int option ){ + return ( _queryOptions & option ) != 0; + } + + int getId(){ + return _id; + } + + @Override + public int putObject(BSONObject o) { + // check max size + int sz = _encoder.writeObject(_buf, o); + if (_mongo != null) { + int maxsize = _mongo.getConnector().getMaxBsonObjectSize(); + maxsize = Math.max(maxsize, Bytes.MAX_OBJECT_SIZE); + if (sz > maxsize) { + throw new MongoInternalException("DBObject of size " + sz + " is over Max BSON size " + _mongo.getMaxBsonObjectSize()); + } + } + return sz; + } + + + public ReadPreference getReadPreference(){ + return _readPref; + } + + private Mongo _mongo; + private PoolOutputBuffer _buffer; + private int _id; + private int _queryOptions = 0; + private ReadPreference _readPref = ReadPreference.PRIMARY; + private DBEncoder _encoder; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/QueryBuilder.java b/src/com/massivecraft/mcore3/lib/mongodb/QueryBuilder.java new file mode 100644 index 00000000..738f76b3 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/QueryBuilder.java @@ -0,0 +1,396 @@ +/* QueryBuilder.java + * + * modified April 11, 2012 by Bryan Reinero + * added $nearSphere, $centerSphere and $within $polygon query support + */ + +/** + * Copyright (C) 2010 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Utility for creating DBObject queries + * @author Julson Lim + * + */ +public class QueryBuilder { + + /** + * Creates a builder with an empty query + */ + public QueryBuilder() { + _query = new BasicDBObject(); + } + + /** + * returns a new QueryBuilder + * @return + */ + public static QueryBuilder start() { + return new QueryBuilder(); + } + + /** + * Creates a new query with a document key + * @param key MongoDB document key + * @return Returns a new QueryBuilder + */ + public static QueryBuilder start(String key) { + return (new QueryBuilder()).put(key); + } + + /** + * Adds a new key to the query if not present yet. + * Sets this key as the current key. + * @param key MongoDB document key + * @return Returns the current QueryBuilder + */ + public QueryBuilder put(String key) { + _currentKey = key; + if(_query.get(key) == null) { + _query.put(_currentKey, new NullObject()); + } + return this; + } + + /** + * Equivalent to QueryBuilder.put(key). Intended for compound query chains to be more readable + * Example: QueryBuilder.start("a").greaterThan(1).and("b").lessThan(3) + * @param key MongoDB document key + * @return Returns the current QueryBuilder with an appended key operand + */ + public QueryBuilder and(String key) { + return put(key); + } + + /** + * Equivalent to the $gt operator + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "greater than" query + */ + public QueryBuilder greaterThan(Object object) { + addOperand(QueryOperators.GT, object); + return this; + } + + /** + * Equivalent to the $gte operator + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "greater than or equals" query + */ + public QueryBuilder greaterThanEquals(Object object) { + addOperand(QueryOperators.GTE, object); + return this; + } + + /** + * Equivalent to the $lt operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "less than" query + */ + public QueryBuilder lessThan(Object object) { + addOperand(QueryOperators.LT, object); + return this; + } + + /** + * Equivalent to the $lte operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "less than or equals" query + */ + public QueryBuilder lessThanEquals(Object object) { + addOperand(QueryOperators.LTE, object); + return this; + } + + /** + * Equivalent of the find({key:value}) + * @param object Value to query + * @return Returns the current QueryBuilder with an appended equality query + */ + public QueryBuilder is(Object object) { + addOperand(null, object); + return this; + } + + /** + * Equivalent of the $ne operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended inequality query + */ + public QueryBuilder notEquals(Object object) { + addOperand(QueryOperators.NE, object); + return this; + } + + /** + * Equivalent of the $in operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "in array" query + */ + public QueryBuilder in(Object object) { + addOperand(QueryOperators.IN, object); + return this; + } + + /** + * Equivalent of the $nin operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "not in array" query + */ + public QueryBuilder notIn(Object object) { + addOperand(QueryOperators.NIN, object); + return this; + } + + /** + * Equivalent of the $mod operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended modulo query + */ + public QueryBuilder mod(Object object) { + addOperand(QueryOperators.MOD, object); + return this; + } + + /** + * Equivalent of the $all operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended "matches all array contents" query + */ + public QueryBuilder all(Object object) { + addOperand(QueryOperators.ALL, object); + return this; + } + + /** + * Equivalent of the $size operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended size operator + */ + public QueryBuilder size(Object object) { + addOperand(QueryOperators.SIZE, object); + return this; + } + + /** + * Equivalent of the $exists operand + * @param object Value to query + * @return Returns the current QueryBuilder with an appended exists operator + */ + public QueryBuilder exists(Object object) { + addOperand(QueryOperators.EXISTS, object); + return this; + } + + /** + * Passes a regular expression for a query + * @param regex Regex pattern object + * @return Returns the current QueryBuilder with an appended regex query + */ + public QueryBuilder regex(Pattern regex) { + addOperand(null, regex); + return this; + } + + /** + * Equivalent of the $within operand, used for geospatial operation + * @param x x coordinate + * @param y y coordinate + * @param radius radius + * @return + */ + public QueryBuilder withinCenter( double x , double y , double radius ){ + addOperand( "$within" , + new BasicDBObject( "$center" , new Object[]{ new Double[]{ x , y } , radius } ) ); + return this; + } + + /** + * Equivalent of the $near operand + * @param x x coordinate + * @param y y coordinate + * @return + */ + public QueryBuilder near( double x , double y ){ + addOperand( "$near" , + new Double[]{ x , y } ); + return this; + } + + /** + * Equivalent of the $near operand + * @param x x coordinate + * @param y y coordinate + * @param maxDistance max distance + * @return + */ + public QueryBuilder near( double x , double y , double maxDistance ){ + addOperand( "$near" , + new Double[]{ x , y , maxDistance } ); + return this; + } + + /** + * Equivalent of the $nearSphere operand + * @param longitude coordinate in decimal degrees + * @param latitude coordinate in decimal degrees + * @return + */ + public QueryBuilder nearSphere( double longitude , double latitude ){ + addOperand( "$nearSphere" , + new Double[]{ longitude , latitude } ); + return this; + } + + /** + * Equivalent of the $nearSphere operand + * @param longitude coordinate in decimal degrees + * @param latitude coordinate in decimal degrees + * @param maxDistance max spherical distance + * @return + */ + public QueryBuilder nearSphere( double longitude , double latitude , double maxDistance ){ + addOperand( "$nearSphere" , + new Double[]{ longitude , latitude , maxDistance } ); + return this; + } + + /** + * Equivalent of the $centerSphere operand + * mostly intended for queries up to a few hundred miles or km. + * @param longitude coordinate in decimal degrees + * @param latitude coordinate in decimal degrees + * @param maxDistance max spherical distance + * @return + */ + public QueryBuilder withinCenterSphere( double longitude , double latitude , double maxDistance ){ + addOperand( "$within" , + new BasicDBObject( "$centerSphere" , new Object[]{ new Double[]{longitude , latitude} , maxDistance } ) ); + return this; + } + + /** + * Equivalent to a $within operand, based on a bounding box using represented by two corners + * + * @param x the x coordinate of the first box corner. + * @param y the y coordinate of the first box corner. + * @param x2 the x coordinate of the second box corner. + * @param y2 the y coordinate of the second box corner. + * @return + */ + public QueryBuilder withinBox(double x, double y, double x2, double y2) { + addOperand( "$within" , + new BasicDBObject( "$box" , new Object[] { new Double[] { x, y }, new Double[] { x2, y2 } } ) ); + return this; + } + + /** + * Equivalent to a $within operand, based on a bounding polygon represented by an array of points + * + * @param points an array of Double[] defining the vertices of the search area + * @return + */ + public QueryBuilder withinPolygon(List points) { + if(points == null || points.isEmpty() || points.size() < 3) + throw new IllegalArgumentException("Polygon insufficient number of vertices defined"); + addOperand( "$within" , + new BasicDBObject( "$polygon" , points ) ); + return this; + } + + /** + * Equivalent to a $or operand + * @param ors + * @return + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public QueryBuilder or( DBObject ... ors ){ + List l = (List)_query.get( "$or" ); + if ( l == null ){ + l = new ArrayList(); + _query.put( "$or" , l ); + } + for ( DBObject o : ors ) + l.add( o ); + return this; + } + + /** + * Equivalent to an $and operand + * @param ands + * @return + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public QueryBuilder and( DBObject ... ands ){ + List l = (List)_query.get( "$and" ); + if ( l == null ){ + l = new ArrayList(); + _query.put( "$and" , l ); + } + for ( DBObject o : ands ) + l.add( o ); + return this; + } + + /** + * Creates a DBObject query to be used for the driver's find operations + * @return Returns a DBObject query instance + * @throws RuntimeException if a key does not have a matching operand + */ + public DBObject get() { + for(String key : _query.keySet()) { + if(_query.get(key) instanceof NullObject) { + throw new QueryBuilderException("No operand for key:" + key); + } + } + return _query; + } + + private void addOperand(String op, Object value) { + if(op == null) { + _query.put(_currentKey, value); + return; + } + + Object storedValue = _query.get(_currentKey); + BasicDBObject operand; + if(!(storedValue instanceof DBObject)) { + operand = new BasicDBObject(); + _query.put(_currentKey, operand); + } else { + operand = (BasicDBObject)_query.get(_currentKey); + } + operand.put(op, value); + } + + @SuppressWarnings("serial") + static class QueryBuilderException extends RuntimeException { + QueryBuilderException(String message) { + super(message); + } + } + private static class NullObject {} + + private DBObject _query; + private String _currentKey; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/QueryOperators.java b/src/com/massivecraft/mcore3/lib/mongodb/QueryOperators.java new file mode 100644 index 00000000..4acbc892 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/QueryOperators.java @@ -0,0 +1,39 @@ +// QueryOperators.java + +/** + * Copyright (C) 2010 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +/** + * MongoDB keywords for various query operations + * @author Julson Lim + * + */ +public class QueryOperators { + public static final String GT = "$gt"; + public static final String GTE = "$gte"; + public static final String LT = "$lt"; + public static final String LTE = "$lte"; + public static final String NE = "$ne"; + public static final String IN = "$in"; + public static final String NIN = "$nin"; + public static final String MOD = "$mod"; + public static final String ALL = "$all"; + public static final String SIZE = "$size"; + public static final String EXISTS = "$exists"; + public static final String WHERE = "$where"; + public static final String NEAR = "$near"; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/RawDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/RawDBObject.java new file mode 100644 index 00000000..1f8266e2 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/RawDBObject.java @@ -0,0 +1,366 @@ +// RawDBObject.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import static com.massivecraft.mcore3.lib.bson.BSON.ARRAY; +import static com.massivecraft.mcore3.lib.bson.BSON.BINARY; +import static com.massivecraft.mcore3.lib.bson.BSON.BOOLEAN; +import static com.massivecraft.mcore3.lib.bson.BSON.CODE; +import static com.massivecraft.mcore3.lib.bson.BSON.CODE_W_SCOPE; +import static com.massivecraft.mcore3.lib.bson.BSON.DATE; +import static com.massivecraft.mcore3.lib.bson.BSON.EOO; +import static com.massivecraft.mcore3.lib.bson.BSON.MAXKEY; +import static com.massivecraft.mcore3.lib.bson.BSON.MINKEY; +import static com.massivecraft.mcore3.lib.bson.BSON.NULL; +import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER; +import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER_INT; +import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER_LONG; +import static com.massivecraft.mcore3.lib.bson.BSON.OBJECT; +import static com.massivecraft.mcore3.lib.bson.BSON.OID; +import static com.massivecraft.mcore3.lib.bson.BSON.REF; +import static com.massivecraft.mcore3.lib.bson.BSON.REGEX; +import static com.massivecraft.mcore3.lib.bson.BSON.STRING; +import static com.massivecraft.mcore3.lib.bson.BSON.SYMBOL; +import static com.massivecraft.mcore3.lib.bson.BSON.TIMESTAMP; +import static com.massivecraft.mcore3.lib.bson.BSON.UNDEFINED; +import static com.massivecraft.mcore3.lib.mongodb.util.MyAsserts.assertEquals; + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; + +/** + * This object wraps the binary object format ("BSON") used for the transport of serialized objects to / from the Mongo database. + */ +public class RawDBObject implements DBObject { + + RawDBObject( ByteBuffer buf ){ + this( buf , 0 ); + assertEquals( _end , _buf.limit() ); + } + + RawDBObject( ByteBuffer buf , int offset ){ + _buf = buf; + _offset = offset; + _end = _buf.getInt( _offset ); + } + + public Object get( String key ){ + Element e = findElement( key ); + if ( e == null ) + return null; + return e.getObject(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Map toMap() { + Map m = new HashMap(); + Iterator i = this.keySet().iterator(); + while (i.hasNext()) { + Object s = i.next(); + m.put(s, this.get(String.valueOf(s))); + } + return m; + } + + public Object put( String key , Object v ){ + throw new RuntimeException( "read only" ); + } + + public void putAll( BSONObject o ){ + throw new RuntimeException( "read only" ); + } + + @SuppressWarnings("rawtypes") + public void putAll( Map m ){ + throw new RuntimeException( "read only" ); + } + + public Object removeField( String key ){ + throw new RuntimeException( "read only" ); + } + + /** + * @deprecated + */ + @Deprecated + public boolean containsKey( String key ){ + return containsField(key); + } + + public boolean containsField( String field ){ + return findElement( field ) != null; + } + + public Set keySet(){ + Set keys = new HashSet(); + + ElementIter i = new ElementIter(); + while ( i.hasNext() ){ + Element e = i.next(); + if ( e.eoo() ) + break; + keys.add( e.fieldName() ); + } + + return keys; + } + + String _readCStr( final int start ){ + return _readCStr( start , null ); + } + + String _readCStr( final int start , final int[] end ){ + synchronized ( _cStrBuf ){ + int pos = 0; + while ( _buf.get( pos + start ) != 0 ){ + _cStrBuf[pos] = _buf.get( pos + start ); + pos++; + if ( pos >= _cStrBuf.length ) + throw new IllegalArgumentException( "c string too big for RawDBObject. so far[" + new String( _cStrBuf ) + "]" ); + + if ( pos + start >= _buf.limit() ){ + StringBuilder sb = new StringBuilder(); + for ( int x=0; x<10; x++ ){ + int y = start + x; + if ( y >= _buf.limit() ) + break; + sb.append( (char)_buf.get( y ) ); + } + throw new IllegalArgumentException( "can't find end of cstring. start:" + start + " pos: " + pos + " [" + sb + "]" ); + } + } + if ( end != null && end.length > 0 ) + end[0] = start + pos; + return new String( _cStrBuf , 0 , pos ); + + } + } + + String _readJavaString( final int start ){ + int size = _buf.getInt( start ) - 1; + + byte[] b = new byte[size]; + + int old = _buf.position(); + _buf.position( start + 4 ); + _buf.get( b , 0 , b.length ); + _buf.position( old ); + + try { + return new String( b , "UTF-8" ); + } + catch ( java.io.UnsupportedEncodingException uee ){ + return new String( b ); + } + } + + /** + * includes 0 at end + */ + int _cStrLength( final int start ){ + int end = start; + while ( _buf.get( end ) != 0 ) + end++; + return 1 + ( end - start ); + } + + Element findElement( String name ){ + ElementIter i = new ElementIter(); + while ( i.hasNext() ){ + Element e = i.next(); + if ( e.fieldName().equals( name ) ) + return e; + } + return null; + } + + public boolean isPartialObject(){ + return false; + } + + + public void markAsPartialObject(){ + throw new RuntimeException( "RawDBObject can't be a partial object" ); + } + + @Override + public String toString(){ + return "Object"; + } + + class Element { + Element( final int start ){ + _start = start; + _type = _buf.get( _start ); + int end[] = new int[1]; + _name = eoo() ? "" : _readCStr( _start + 1 , end ); + + int size = 1 + ( end[0] - _start); // 1 for the end of the string + _dataStart = _start + size; + + switch ( _type ){ + case MAXKEY: + case MINKEY: + case EOO: + case UNDEFINED: + case NULL: + break; + case BOOLEAN: + size += 1; + break; + case DATE: + case NUMBER: + case NUMBER_LONG: + size += 8; + break; + case NUMBER_INT: + size += 4; + break; + case OID: + size += 12; + break; + case REF: + size += 12; + size += 4 + _buf.getInt( _dataStart ); + break; + case SYMBOL: + case CODE: + case STRING: + size += 4 + _buf.getInt( _dataStart ); + break; + case CODE_W_SCOPE: + case ARRAY: + case OBJECT: + size += _buf.getInt( _dataStart ); + break; + case BINARY: + size += 4 + _buf.getInt( _dataStart ) + 1; + break; + case REGEX: + int first = _cStrLength( _dataStart ); + int second = _cStrLength( _dataStart + first ); + size += first + second; + break; + case TIMESTAMP: + size += 8; + break; + default: + throw new RuntimeException( "RawDBObject can't size type " + _type ); + } + _size = size; + } + + String fieldName(){ + return _name; + } + + boolean eoo(){ + return _type == EOO || _type == MAXKEY; + } + + int size(){ + return _size; + } + + Object getObject(){ + + if ( _cached != null ) + return _cached; + + switch ( _type ){ + case NUMBER: + return _buf.getDouble( _dataStart ); + case NUMBER_INT: + return _buf.getInt( _dataStart ); + case OID: + return new ObjectId( _buf.getInt( _dataStart ) , _buf.getInt( _dataStart + 4 ) , _buf.getInt( _dataStart + 8 ) ); + case CODE: + case CODE_W_SCOPE: + throw new RuntimeException( "can't handle code" ); + case SYMBOL: + case STRING: + return _readJavaString( _dataStart ); + case DATE: + return new Date( _buf.getLong( _dataStart ) ); + case REGEX: + //int[] endPos = new int[1]; + //String first = _readCStr( _dataStart , endPos ); + //return new JSRegex( first , _readCStr( 1 + endPos[0] ) ); + throw new RuntimeException( "can't handle regex" ); + case BINARY: + throw new RuntimeException( "can't inspect binary in db" ); + case BOOLEAN: + return _buf.get( _dataStart ) > 0; + case ARRAY: + case OBJECT: + throw new RuntimeException( "can't handle emebdded objects" ); + case NULL: + case EOO: + case MAXKEY: + case MINKEY: + case UNDEFINED: + return null; + } + throw new RuntimeException( "can't decode type " + _type ); + } + + final int _start; + final byte _type; + final String _name; + final int _dataStart; + final int _size; + + Object _cached; + } + + class ElementIter { + + ElementIter(){ + _pos = _offset + 4; + } + + boolean hasNext(){ + return ! _done && _pos < _buf.limit(); + } + + Element next(){ + Element e = new Element( _pos ); + _done = e.eoo(); + + _pos += e.size(); + return e; + } + + int _pos; + boolean _done = false; + } + + final ByteBuffer _buf; + final int _offset; + final int _end; + private final static byte[] _cStrBuf = new byte[1024]; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/ReadPreference.java b/src/com/massivecraft/mcore3/lib/mongodb/ReadPreference.java new file mode 100644 index 00000000..b5970da8 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/ReadPreference.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2008 - 2011 10gen, Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.util.Map; + +public class ReadPreference { + public static class PrimaryReadPreference extends ReadPreference { + private PrimaryReadPreference() {} + @Override + public String toString(){ + return "ReadPreference.PRIMARY" ; + } + } + + public static class SecondaryReadPreference extends ReadPreference { + private SecondaryReadPreference() {} + @Override + public String toString(){ + return "ReadPreference.SECONDARY"; + } + } + + public static class TaggedReadPreference extends ReadPreference { + public TaggedReadPreference( DBObject tags ) { + _tags = tags; + } + + public TaggedReadPreference( Map tags ) { + _tags = new BasicDBObject( tags ); + } + + public DBObject getTags(){ + return _tags; + } + + @Override + public String toString(){ + return getTags().toString(); + } + + private final DBObject _tags; + + } + + public static ReadPreference PRIMARY = new PrimaryReadPreference(); + + public static ReadPreference SECONDARY = new SecondaryReadPreference(); + + /* + public static ReadPreference withTags(Map tags) { + return new TaggedReadPreference( tags ); + } + + public static ReadPreference withTags( final DBObject tags ) { + return new TaggedReadPreference( tags ); + } + */ +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/ReflectionDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/ReflectionDBObject.java new file mode 100644 index 00000000..4985080a --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/ReflectionDBObject.java @@ -0,0 +1,292 @@ +// ReflectionDBObject.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import com.massivecraft.mcore3.lib.bson.BSONObject; + +/** + * This class enables to map simple Class fields to a BSON object fields + */ +public abstract class ReflectionDBObject implements DBObject { + + public Object get( String key ){ + return getWrapper().get( this , key ); + } + + public Set keySet(){ + return getWrapper().keySet(); + } + + /** + * @deprecated + */ + @Deprecated + public boolean containsKey( String s ){ + return containsField( s ); + } + + public boolean containsField( String s ){ + return getWrapper().containsKey( s ); + } + + public Object put( String key , Object v ){ + return getWrapper().set( this , key , v ); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void putAll( Map m ){ + for ( Map.Entry entry : (Set)m.entrySet() ){ + put( entry.getKey().toString() , entry.getValue() ); + } + } + + public void putAll( BSONObject o ){ + for ( String k : o.keySet() ){ + put( k , o.get( k ) ); + } + } + + /** + * Gets the _id + * @return + */ + public Object get_id(){ + return _id; + } + + /** + * Sets the _id + * @param id + */ + public void set_id( Object id ){ + _id = id; + } + + public boolean isPartialObject(){ + return false; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Map toMap() { + Map m = new HashMap(); + Iterator i = this.keySet().iterator(); + while (i.hasNext()) { + Object s = i.next(); + m.put(s, this.get(s+"")); + } + return m; + } + + /** + * ReflectionDBObjects can't be partial + */ + public void markAsPartialObject(){ + throw new RuntimeException( "ReflectionDBObjects can't be partial" ); + } + + /** + * can't remove from a ReflectionDBObject + * @param key + * @return + */ + public Object removeField( String key ){ + throw new RuntimeException( "can't remove from a ReflectionDBObject" ); + } + + JavaWrapper getWrapper(){ + if ( _wrapper != null ) + return _wrapper; + + _wrapper = getWrapper( this.getClass() ); + return _wrapper; + } + + JavaWrapper _wrapper; + Object _id; + + /** + * Represents a wrapper around the DBObject to interface with the Class fields + */ + public static class JavaWrapper { + @SuppressWarnings("rawtypes") + JavaWrapper( Class c ){ + _class = c; + _name = c.getName(); + + _fields = new TreeMap(); + for ( Method m : c.getMethods() ){ + if ( ! ( m.getName().startsWith( "get" ) || m.getName().startsWith( "set" ) ) ) + continue; + + String name = m.getName().substring(3); + if ( name.length() == 0 || IGNORE_FIELDS.contains( name ) ) + continue; + + Class type = m.getName().startsWith( "get" ) ? m.getReturnType() : m.getParameterTypes()[0]; + + FieldInfo fi = _fields.get( name ); + if ( fi == null ){ + fi = new FieldInfo( name , type ); + _fields.put( name , fi ); + } + + if ( m.getName().startsWith( "get" ) ) + fi._getter = m; + else + fi._setter = m; + } + + Set names = new HashSet( _fields.keySet() ); + for ( String name : names ) + if ( ! _fields.get( name ).ok() ) + _fields.remove( name ); + + _keys = Collections.unmodifiableSet( _fields.keySet() ); + } + + public Set keySet(){ + return _keys; + } + + /** + * @deprecated + */ + @Deprecated + public boolean containsKey( String key ){ + return _keys.contains( key ); + } + + public Object get( ReflectionDBObject t , String name ){ + FieldInfo i = _fields.get( name ); + if ( i == null ) + return null; + try { + return i._getter.invoke( t ); + } + catch ( Exception e ){ + throw new RuntimeException( "could not invoke getter for [" + name + "] on [" + _name + "]" , e ); + } + } + + public Object set( ReflectionDBObject t , String name , Object val ){ + FieldInfo i = _fields.get( name ); + if ( i == null ) + throw new IllegalArgumentException( "no field [" + name + "] on [" + _name + "]" ); + try { + return i._setter.invoke( t , val ); + } + catch ( Exception e ){ + throw new RuntimeException( "could not invoke setter for [" + name + "] on [" + _name + "]" , e ); + } + } + + @SuppressWarnings("rawtypes") + Class getInternalClass( String path ){ + String cur = path; + String next = null; + final int idx = path.indexOf( "." ); + if ( idx >= 0 ){ + cur = path.substring( 0 , idx ); + next = path.substring( idx + 1 ); + } + + FieldInfo fi = _fields.get( cur ); + if ( fi == null ) + return null; + + if ( next == null ) + return fi._class; + + JavaWrapper w = getWrapperIfReflectionObject( fi._class ); + if ( w == null ) + return null; + return w.getInternalClass( next ); + } + + @SuppressWarnings("rawtypes") + final Class _class; + final String _name; + final Map _fields; + final Set _keys; + } + + static class FieldInfo { + @SuppressWarnings("rawtypes") + FieldInfo( String name , Class c ){ + _name = name; + _class = c; + } + + boolean ok(){ + return + _getter != null && + _setter != null; + } + + final String _name; + @SuppressWarnings("rawtypes") + final Class _class; + Method _getter; + Method _setter; + } + + /** + * Returns the wrapper if this object can be assigned from this class + * @param c + * @return + */ + @SuppressWarnings("rawtypes") + public static JavaWrapper getWrapperIfReflectionObject( Class c ){ + if ( ReflectionDBObject.class.isAssignableFrom( c ) ) + return getWrapper( c ); + return null; + } + + /** + * Returns an existing Wrapper instance associated with a class, or creates a new one. + * @param c + * @return + */ + @SuppressWarnings("rawtypes") + public static JavaWrapper getWrapper( Class c ){ + JavaWrapper w = _wrappers.get( c ); + if ( w == null ){ + w = new JavaWrapper( c ); + _wrappers.put( c , w ); + } + return w; + } + + @SuppressWarnings("rawtypes") + private static final Map _wrappers = Collections.synchronizedMap( new HashMap() ); + private static final Set IGNORE_FIELDS = new HashSet(); + static { + IGNORE_FIELDS.add( "Int" ); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/ReplicaSetStatus.java b/src/com/massivecraft/mcore3/lib/mongodb/ReplicaSetStatus.java new file mode 100644 index 00000000..18197142 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/ReplicaSetStatus.java @@ -0,0 +1,834 @@ +// ReplicaSetStatus.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.util.annotations.Immutable; +import com.massivecraft.mcore3.lib.bson.util.annotations.ThreadSafe; +import com.massivecraft.mcore3.lib.mongodb.util.JSON; + + +import java.net.UnknownHostException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +// TODO: +// pull config to get +// priority +// slave delay + +/** + * Keeps replica set status. Maintains a background thread to ping all members of the set to keep the status current. + */ +@ThreadSafe +public class ReplicaSetStatus { + + static final Logger _rootLogger = Logger.getLogger( "com.mongodb.ReplicaSetStatus" ); + + ReplicaSetStatus( Mongo mongo, List initial ){ + _mongoOptions = _mongoOptionsDefaults.copy(); + _mongoOptions.socketFactory = mongo._options.socketFactory; + _mongo = mongo; + _updater = new Updater(initial); + } + + void start() { + _updater.start(); + } + + public String getName() { + return _setName.get(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{replSetName: ").append(_setName.get()); + sb.append(", nextResolveTime: ").append(new Date(_updater.getNextResolveTime()).toString()); + sb.append(", members: ").append(_replicaSetHolder); + sb.append(", updaterIntervalMS: ").append(updaterIntervalMS); + sb.append(", updaterIntervalNoMasterMS: ").append(updaterIntervalNoMasterMS); + sb.append(", slaveAcceptableLatencyMS: ").append(slaveAcceptableLatencyMS); + sb.append(", inetAddrCacheMS: ").append(inetAddrCacheMS); + sb.append(", latencySmoothFactor: ").append(latencySmoothFactor); + sb.append("}"); + + return sb.toString(); + } + + void _checkClosed(){ + if ( _closed ) + throw new IllegalStateException( "ReplicaSetStatus closed" ); + } + + /** + * @return master or null if don't have one + */ + public ServerAddress getMaster(){ + Node n = getMasterNode(); + if ( n == null ) + return null; + return n.getServerAddress(); + } + + Node getMasterNode(){ + _checkClosed(); + return _replicaSetHolder.get().getMaster(); + } + + /** + * @param srv + * the server to compare + * @return indication if the ServerAddress is the current Master/Primary + */ + public boolean isMaster(ServerAddress srv) { + if (srv == null) + return false; + + return srv.equals(getMaster()); + } + + /** + * @param tags tags map + * @return a good secondary by tag value or null if can't find one + */ + ServerAddress getASecondary( DBObject tags ) { + // store the reference in local, so that it doesn't change out from under us while looping + List tagList = new ArrayList(); + for ( String key : tags.keySet() ) { + tagList.add(new Tag(key, tags.get(key).toString())); + } + Node node = _replicaSetHolder.get().getASecondary(tagList); + if (node != null) { + return node.getServerAddress(); + } + return null; + } + + /** + * @return a good secondary or null if can't find one + */ + ServerAddress getASecondary() { + Node node = _replicaSetHolder.get().getASecondary(); + if (node == null) { + return null; + } + return node._addr; + } + + boolean hasServerUp() { + for (Node node : _replicaSetHolder.get().getAll()) { + if (node.isOk()) { + return true; + } + } + return false; + } + + // Simple abstraction over a volatile ReplicaSet reference that starts as null. The get method blocks until members + // is not null. The set method notifies all, thus waking up all getters. + @ThreadSafe + static class ReplicaSetHolder { + private volatile ReplicaSet members; + + // blocks until replica set is set. + synchronized ReplicaSet get() { + while (members == null) { + try { + wait(); + } + catch (InterruptedException e) { + throw new MongoException("Interrupted while waiting for next update to replica set status", e); + } + } + return members; + } + + // set the replica set to a non-null value and notifies all threads waiting. + synchronized void set(ReplicaSet members) { + if (members == null) { + throw new IllegalArgumentException("members can not be null"); + } + this.members = members; + notifyAll(); + } + + // blocks until the replica set is set again + synchronized void waitForNextUpdate() { + try { + wait(); + } + catch (InterruptedException e) { + throw new MongoException("Interrupted while waiting for next update to replica set status", e); + } + } + + public synchronized void close() { + this.members = null; + notifyAll(); + } + + public String toString() { + ReplicaSet cur = this.members; + if (cur != null) { + return cur.toString(); + } + return "none"; + } + } + + // Immutable snapshot state of a replica set. Since the nodes don't change state, this class pre-computes the list + // of good secondaries so that choosing a random good secondary is dead simple + @Immutable + static class ReplicaSet { + final List all; + final Random random; + final List goodSecondaries; + final Map> goodSecondariesByTagMap; + final Node master; + + public ReplicaSet(List nodeList, Random random, int acceptableLatencyMS) { + this.random = random; + this.all = Collections.unmodifiableList(new ArrayList(nodeList)); + this.goodSecondaries = + Collections.unmodifiableList(calculateGoodSecondaries(all, calculateBestPingTime(all), acceptableLatencyMS)); + Set uniqueTags = new HashSet(); + for (Node curNode : all) { + for (Tag curTag : curNode._tags) { + uniqueTags.add(curTag); + } + } + Map> goodSecondariesByTagMap = new HashMap>(); + for (Tag curTag : uniqueTags) { + List taggedMembers = getMembersByTag(all, curTag); + goodSecondariesByTagMap.put(curTag, + Collections.unmodifiableList(calculateGoodSecondaries(taggedMembers, + calculateBestPingTime(taggedMembers), acceptableLatencyMS))); + } + this.goodSecondariesByTagMap = Collections.unmodifiableMap(goodSecondariesByTagMap); + master = findMaster(); + + } + + public List getAll() { + return all; + } + + public boolean hasMaster() { + return getMaster() != null; + } + + public Node getMaster() { + return master; + } + + public int getMaxBsonObjectSize() { + if (hasMaster()) { + return getMaster().getMaxBsonObjectSize(); + } else { + return Bytes.MAX_OBJECT_SIZE; + } + } + + public Node getASecondary() { + if (goodSecondaries.isEmpty()) { + return null; + } + return goodSecondaries.get(random.nextInt(goodSecondaries.size())); + } + + public Node getASecondary(List tags) { + for (Tag tag : tags) { + List goodSecondariesByTag = goodSecondariesByTagMap.get(tag); + if (goodSecondariesByTag != null) { + Node node = goodSecondariesByTag.get(random.nextInt(goodSecondariesByTag.size())); + if (node != null) { + return node; + } + } + } + return null; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[ "); + for (Node node : getAll()) + sb.append(node.toJSON()).append(","); + sb.setLength(sb.length() - 1); //remove last comma + sb.append(" ]"); + return sb.toString(); + } + + public Node findMaster() { + for (Node node : all) { + if (node.master()) + return node; + } + return null; + } + + + static float calculateBestPingTime(List members) { + float bestPingTime = Float.MAX_VALUE; + for (Node cur : members) { + if (!cur.secondary()) { + continue; + } + if (cur._pingTime < bestPingTime) { + bestPingTime = cur._pingTime; + } + } + return bestPingTime; + } + + static List calculateGoodSecondaries(List members, float bestPingTime, int acceptableLatencyMS) { + List goodSecondaries = new ArrayList(members.size()); + for (Node cur : members) { + if (!cur.secondary()) { + continue; + } + if (cur._pingTime - acceptableLatencyMS <= bestPingTime ) { + goodSecondaries.add(cur); + } + } + return goodSecondaries; + } + + static List getMembersByTag(List members, Tag tag) { + List membersByTag = new ArrayList(); + + for (Node cur : members) { + if (cur._tags.contains(tag)) { + membersByTag.add(cur); + } + } + + return membersByTag; + } + } + + // Represents the state of a node in the replica set. Instances of this class are immutable. + @Immutable + static class Node { + Node(ServerAddress addr, Set names, float pingTime, boolean ok, boolean isMaster, boolean isSecondary, + LinkedHashMap tags, int maxBsonObjectSize) { + this._addr = addr; + this._names = Collections.unmodifiableSet(new HashSet(names)); + this._pingTime = pingTime; + this._ok = ok; + this._isMaster = isMaster; + this._isSecondary = isSecondary; + this._tags = Collections.unmodifiableSet(getTagsFromMap(tags)); + this._maxBsonObjectSize = maxBsonObjectSize; + } + + private static Set getTagsFromMap(LinkedHashMap tagMap) { + Set tagSet = new HashSet(); + for (Map.Entry curEntry : tagMap.entrySet()) { + tagSet.add(new Tag(curEntry.getKey(), curEntry.getValue())); + } + return tagSet; + } + + public boolean isOk() { + return _ok; + } + + public boolean master(){ + return _ok && _isMaster; + } + + public int getMaxBsonObjectSize() { + return _maxBsonObjectSize; + } + + public boolean secondary(){ + return _ok && _isSecondary; + } + + public ServerAddress getServerAddress() { + return _addr; + } + + public Set getNames() { + return _names; + } + + public Set getTags() { + return _tags; + } + + public String toJSON(){ + StringBuilder buf = new StringBuilder(); + buf.append( "{ address:'" ).append( _addr ).append( "', " ); + buf.append( "ok:" ).append( _ok ).append( ", " ); + buf.append( "ping:" ).append( _pingTime ).append( ", " ); + buf.append( "isMaster:" ).append( _isMaster ).append( ", " ); + buf.append( "isSecondary:" ).append( _isSecondary ).append( ", " ); + buf.append( "maxBsonObjectSize:" ).append( _maxBsonObjectSize ).append( ", " ); + if(_tags != null && _tags.size() > 0) + buf.append( "tags:" ).append( JSON.serialize(_tags ) ); + buf.append("}"); + + return buf.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Node node = (Node) o; + + if (_isMaster != node._isMaster) return false; + if (_maxBsonObjectSize != node._maxBsonObjectSize) return false; + if (_isSecondary != node._isSecondary) return false; + if (_ok != node._ok) return false; + if (Float.compare(node._pingTime, _pingTime) != 0) return false; + if (!_addr.equals(node._addr)) return false; + if (!_names.equals(node._names)) return false; + if (!_tags.equals(node._tags)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = _addr.hashCode(); + result = 31 * result + (_pingTime != +0.0f ? Float.floatToIntBits(_pingTime) : 0); + result = 31 * result + _names.hashCode(); + result = 31 * result + _tags.hashCode(); + result = 31 * result + (_ok ? 1 : 0); + result = 31 * result + (_isMaster ? 1 : 0); + result = 31 * result + (_isSecondary ? 1 : 0); + result = 31 * result + _maxBsonObjectSize; + return result; + } + + private final ServerAddress _addr; + private final float _pingTime; + private final Set _names; + private final Set _tags; + private final boolean _ok; + private final boolean _isMaster; + private final boolean _isSecondary; + private final int _maxBsonObjectSize; + } + + // Simple class to hold a single tag, both key and value + @Immutable + static final class Tag { + final String key; + final String value; + + Tag(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Tag tag = (Tag) o; + + if (key != null ? !key.equals(tag.key) : tag.key != null) return false; + if (value != null ? !value.equals(tag.value) : tag.value != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = key != null ? key.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + } + + // Represents the state of a node in the replica set. Instances of this class are mutable. + static class UpdatableNode { + + UpdatableNode(ServerAddress addr, + List all, + AtomicReference logger, + Mongo mongo, + MongoOptions mongoOptions, + AtomicReference setName, + AtomicReference lastPrimarySignal) + { + _addr = addr; + _all = all; + _mongoOptions = mongoOptions; + _port = new DBPort( addr , null , _mongoOptions ); + _names.add( addr.toString() ); + _logger = logger; + _mongo = mongo; + + _setName = setName; + _lastPrimarySignal = lastPrimarySignal; + } + + private void updateAddr() { + try { + if (_addr.updateInetAddress()) { + // address changed, need to use new ports + _port = new DBPort(_addr, null, _mongoOptions); + _mongo.getConnector().updatePortPool(_addr); + _logger.get().log(Level.INFO, "Address of host " + _addr.toString() + " changed to " + _addr.getSocketAddress().toString()); + } + } catch (UnknownHostException ex) { + _logger.get().log(Level.WARNING, null, ex); + } + } + + @SuppressWarnings("rawtypes") + synchronized void update(Set seenNodes){ + try { + long start = System.nanoTime(); + CommandResult res = _port.runCommand( _mongo.getDB("admin") , _isMasterCmd ); + long end = System.nanoTime(); + float newPingMS = (end - start) / 1000000F; + if (!successfullyContacted) + _pingTimeMS = newPingMS; + else + _pingTimeMS = _pingTimeMS + ((newPingMS - _pingTimeMS) / latencySmoothFactor); + + _rootLogger.log( Level.FINE , "Latency to " + _addr + " actual=" + newPingMS + " smoothed=" + _pingTimeMS); + + successfullyContacted = true; + + if ( res == null ){ + throw new MongoInternalException("Invalid null value returned from isMaster"); + } + + if (!_ok) { + _logger.get().log( Level.INFO , "Server seen up: " + _addr ); + } + _ok = true; + _isMaster = res.getBoolean( "ismaster" , false ); + _isSecondary = res.getBoolean( "secondary" , false ); + _lastPrimarySignal.set( res.getString( "primary" ) ); + + if ( res.containsField( "hosts" ) ){ + for ( Object x : (List)res.get("hosts") ){ + String host = x.toString(); + UpdatableNode node = _addIfNotHere(host); + if (node != null && seenNodes != null) + seenNodes.add(node); + } + } + + if ( res.containsField( "passives" ) ){ + for ( Object x : (List)res.get("passives") ){ + String host = x.toString(); + UpdatableNode node = _addIfNotHere(host); + if (node != null && seenNodes != null) + seenNodes.add(node); + } + } + + // Tags were added in 2.0 but may not be present + if (res.containsField( "tags" )) { + DBObject tags = (DBObject) res.get( "tags" ); + for ( String key : tags.keySet() ) { + _tags.put( key, tags.get( key ).toString() ); + } + } + + // max size was added in 1.8 + if (res.containsField("maxBsonObjectSize")) { + _maxBsonObjectSize = (Integer) res.get("maxBsonObjectSize"); + } else { + _maxBsonObjectSize = Bytes.MAX_OBJECT_SIZE; + } + + if (res.containsField("setName")) { + String setName = res.get( "setName" ).toString(); + if ( _setName.get() == null ){ + _setName.set(setName); + _logger.set( Logger.getLogger( _rootLogger.getName() + "." + setName)); + } + else if ( !_setName.get().equals( setName ) ){ + _logger.get().log( Level.SEVERE , "mismatch set name old: " + _setName.get() + " new: " + setName ); + } + } + + } + catch ( Exception e ){ + if (_ok) { + _logger.get().log( Level.WARNING , "Server seen down: " + _addr, e ); + } else if (Math.random() < 0.1) { + _logger.get().log( Level.WARNING , "Server seen down: " + _addr, e ); + } + _ok = false; + } + } + + UpdatableNode _addIfNotHere( String host ){ + UpdatableNode n = findNode( host, _all, _logger ); + if ( n == null ){ + try { + n = new UpdatableNode( new ServerAddress( host ), _all, _logger, _mongo, _mongoOptions, _setName, _lastPrimarySignal ); + _all.add( n ); + } + catch ( UnknownHostException un ){ + _logger.get().log( Level.WARNING , "couldn't resolve host [" + host + "]" ); + } + } + return n; + } + + private UpdatableNode findNode( String host, List members, AtomicReference logger ){ + for (UpdatableNode node : members) + if (node._names.contains(host)) + return node; + + ServerAddress addr; + try { + addr = new ServerAddress( host ); + } + catch ( UnknownHostException un ){ + logger.get().log( Level.WARNING , "couldn't resolve host [" + host + "]" ); + return null; + } + + for (UpdatableNode node : members) { + if (node._addr.equals(addr)) { + node._names.add(host); + return node; + } + } + + return null; + } + + public void close() { + _port.close(); + _port = null; + } + + final ServerAddress _addr; + private final Set _names = Collections.synchronizedSet( new HashSet() ); + private DBPort _port; // we have our own port so we can set different socket options and don't have to owrry about the pool + final LinkedHashMap _tags = new LinkedHashMap( ); + + boolean successfullyContacted = false; + boolean _ok = false; + float _pingTimeMS = 0; + + boolean _isMaster = false; + boolean _isSecondary = false; + + int _maxBsonObjectSize; + + double _priority = 0; + + private final AtomicReference _logger; + private final MongoOptions _mongoOptions; + private final Mongo _mongo; + private final AtomicReference _setName; + private final AtomicReference _lastPrimarySignal; + private final List _all; + } + + // Thread that monitors the state of the replica set. This thread is responsible for setting a new ReplicaSet + // instance on ReplicaSetStatus.members every pass through the members of the set. + class Updater extends Thread { + + Updater(List initial){ + super( "ReplicaSetStatus:Updater" ); + setDaemon( true ); + _all = new ArrayList(initial.size()); + for ( ServerAddress addr : initial ){ + _all.add( new UpdatableNode( addr, _all, _logger, _mongo, _mongoOptions, _setName, _lastPrimarySignal ) ); + } + _nextResolveTime = System.currentTimeMillis() + inetAddrCacheMS; + } + + @Override + public void run() { + try { + while (!Thread.interrupted()) { + int curUpdateIntervalMS = updaterIntervalNoMasterMS; + + try { + updateAll(); + + updateInetAddresses(); + + ReplicaSet replicaSet = new ReplicaSet(createNodeList(), _random, slaveAcceptableLatencyMS); + _replicaSetHolder.set(replicaSet); + + if (replicaSet.hasMaster()) { + _mongo.getConnector().setMaster(replicaSet.getMaster()); + curUpdateIntervalMS = updaterIntervalMS; + } + } catch (Exception e) { + _logger.get().log(Level.WARNING, "couldn't do update pass", e); + } + + Thread.sleep(curUpdateIntervalMS); + } + } + catch (InterruptedException e) { + // Allow thread to exit + } + + _replicaSetHolder.close(); + closeAllNodes(); + } + + public long getNextResolveTime() { + return _nextResolveTime; + } + + public synchronized void updateAll(){ + HashSet seenNodes = new HashSet(); + + for (int i = 0; i < _all.size(); i++) { + _all.get(i).update(seenNodes); + } + + if (seenNodes.size() > 0) { + // not empty, means that at least 1 server gave node list + // remove unused hosts + Iterator it = _all.iterator(); + while (it.hasNext()) { + if (!seenNodes.contains(it.next())) + it.remove(); + } + } + } + + private List createNodeList() { + List nodeList = new ArrayList(_all.size()); + for (UpdatableNode cur : _all) { + nodeList.add(new Node(cur._addr, cur._names, cur._pingTimeMS, cur._ok, cur._isMaster, cur._isSecondary, cur._tags, cur._maxBsonObjectSize)); + } + return nodeList; + } + + private void updateInetAddresses() { + long now = System.currentTimeMillis(); + if (inetAddrCacheMS > 0 && _nextResolveTime < now) { + _nextResolveTime = now + inetAddrCacheMS; + for (UpdatableNode node : _all) { + node.updateAddr(); + } + } + } + + private void closeAllNodes() { + for (UpdatableNode node : _all) { + try { + node.close(); + } catch (final Throwable t) { /* nada */ } + } + } + + private final List _all; + private volatile long _nextResolveTime; + private final Random _random = new Random(); + } + + /** + * Ensures that we have the current master, if there is one. If the current snapshot of the replica set + * has no master, this method waits one cycle to find a new master, and returns it if found, or null if not. + * + * @return address of the current master, or null if there is none + */ + Node ensureMaster() { + if (_closed) { + return null; + } + + Node masterNode = getMasterNode(); + if (masterNode != null) { + return masterNode; + } + + _replicaSetHolder.waitForNextUpdate(); + + masterNode = getMasterNode(); + if (masterNode != null) { + return masterNode; + } + + return null; + } + + List getServerAddressList() { + List addrs = new ArrayList(); + for (Node node : _replicaSetHolder.get().getAll()) + addrs.add(node.getServerAddress()); + return addrs; + } + + void close() { + _closed = true; + _updater.interrupt(); + } + + /** + * Gets the maximum size for a BSON object supported by the current master server. + * Note that this value may change over time depending on which server is master. + * @return the maximum size, or 0 if not obtained from servers yet. + */ + public int getMaxBsonObjectSize() { + return _replicaSetHolder.get().getMaxBsonObjectSize(); + } + + final ReplicaSetHolder _replicaSetHolder = new ReplicaSetHolder(); + + final Updater _updater; + private final Mongo _mongo; + private final AtomicReference _setName = new AtomicReference(); // null until init + + // will get changed to use set name once its found + private final AtomicReference _logger = new AtomicReference(_rootLogger); + + private final AtomicReference _lastPrimarySignal = new AtomicReference(); + private volatile boolean _closed; + + final static int updaterIntervalMS; + final static int updaterIntervalNoMasterMS; + final static int slaveAcceptableLatencyMS; + final static int inetAddrCacheMS; + final static float latencySmoothFactor; + + private final MongoOptions _mongoOptions; + private static final MongoOptions _mongoOptionsDefaults = new MongoOptions(); + + static { + updaterIntervalMS = Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalMS", "5000")); + updaterIntervalNoMasterMS = Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalNoMasterMS", "10")); + slaveAcceptableLatencyMS = Integer.parseInt(System.getProperty("com.mongodb.slaveAcceptableLatencyMS", "15")); + inetAddrCacheMS = Integer.parseInt(System.getProperty("com.mongodb.inetAddrCacheMS", "300000")); + latencySmoothFactor = Float.parseFloat(System.getProperty("com.mongodb.latencySmoothFactor", "4")); + _mongoOptionsDefaults.connectTimeout = Integer.parseInt(System.getProperty("com.mongodb.updaterConnectTimeoutMS", "20000")); + _mongoOptionsDefaults.socketTimeout = Integer.parseInt(System.getProperty("com.mongodb.updaterSocketTimeoutMS", "20000")); + } + + static final DBObject _isMasterCmd = new BasicDBObject( "ismaster" , 1 ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/Response.java b/src/com/massivecraft/mcore3/lib/mongodb/Response.java new file mode 100644 index 00000000..595b7421 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/Response.java @@ -0,0 +1,199 @@ +// Response.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +// Bson +import com.massivecraft.mcore3.lib.bson.io.Bits; + +// Java +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +class Response { + + Response( ServerAddress addr , DBCollection collection , InputStream in, DBDecoder decoder) + throws IOException { + + _host = addr; + + final byte [] b = new byte[36]; + Bits.readFully(in, b); + int pos = 0; + + _len = Bits.readInt(b, pos); + pos += 4; + + if (_len > MAX_LENGTH) + throw new IllegalArgumentException( "response too long: " + _len ); + + _id = Bits.readInt(b, pos); + pos += 4; + + _responseTo = Bits.readInt(b, pos); + pos += 4; + + _operation = Bits.readInt(b, pos); + pos += 4; + + _flags = Bits.readInt(b, pos); + pos += 4; + + _cursor = Bits.readLong(b, pos); + pos += 8; + + _startingFrom = Bits.readInt(b, pos); + pos += 4; + + _num = Bits.readInt(b, pos); + pos += 4; + + final MyInputStream user = new MyInputStream( in , _len - b.length ); + + if ( _num < 2 ) + _objects = new LinkedList(); + else + _objects = new ArrayList( _num ); + + for ( int i=0; i < _num; i++ ){ + if ( user._toGo < 5 ) + throw new IOException( "should have more objects, but only " + user._toGo + " bytes left" ); + // TODO: By moving to generics, you can remove these casts (and requirement to impl DBOBject). + + _objects.add( decoder.decode( user, collection ) ); + } + + if ( user._toGo != 0 ) + throw new IOException( "finished reading objects but still have: " + user._toGo + " bytes to read!' " ); + + if ( _num != _objects.size() ) + throw new RuntimeException( "something is really broken" ); + } + + public int size(){ + return _num; + } + + public ServerAddress serverUsed() { + return _host; + } + + public DBObject get( int i ){ + return _objects.get( i ); + } + + public Iterator iterator(){ + return _objects.iterator(); + } + + public boolean hasGetMore( int queryOptions ){ + if ( _cursor == 0 ) + return false; + + if ( _num > 0 ) + return true; + + if ( ( queryOptions & Bytes.QUERYOPTION_TAILABLE ) == 0 ) + return false; + + // have a tailable cursor, it is always possible to call get more + return true; + } + + public long cursor(){ + return _cursor; + } + + public ServerError getError(){ + if ( _num != 1 ) + return null; + + DBObject obj = get(0); + + if ( ServerError.getMsg( obj , null ) == null ) + return null; + + return new ServerError( obj ); + } + + static class MyInputStream extends InputStream { + MyInputStream( InputStream in , int max ){ + _in = in; + _toGo = max; + } + + public int available() + throws IOException { + return _in.available(); + } + + public int read() + throws IOException { + + if ( _toGo <= 0 ) + return -1; + + int val = _in.read(); + _toGo--; + + return val; + } + + public int read(byte[] b, int off, int len) + throws IOException { + + if ( _toGo <= 0 ) + return -1; + + int n = _in.read(b, off, Math.min(_toGo, len)); + _toGo -= n; + return n; + } + + public void close(){ + throw new RuntimeException( "can't close thos" ); + } + + final InputStream _in; + private int _toGo; + } + + public String toString(){ + return "flags:" + _flags + " _cursor:" + _cursor + " _startingFrom:" + _startingFrom + " _num:" + _num ; + } + + final ServerAddress _host; + + final int _len; + final int _id; + final int _responseTo; + final int _operation; + + final int _flags; + long _cursor; + final int _startingFrom; + final int _num; + + final List _objects; + + private static final int MAX_LENGTH = ( 32 * 1024 * 1024 ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/ServerAddress.java b/src/com/massivecraft/mcore3/lib/mongodb/ServerAddress.java new file mode 100644 index 00000000..680ede10 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/ServerAddress.java @@ -0,0 +1,207 @@ +// ServerAddress.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +/** + * mongo server address + */ +public class ServerAddress { + + /** + * Creates a ServerAddress with default host and port + * @throws UnknownHostException + */ + public ServerAddress() + throws UnknownHostException { + this( defaultHost() , defaultPort() ); + } + + /** + * Creates a ServerAddress with default port + * @param host hostname + * @throws UnknownHostException + */ + public ServerAddress( String host ) + throws UnknownHostException { + this( host , defaultPort() ); + } + + /** + * Creates a ServerAddress + * @param host hostname + * @param port mongod port + * @throws UnknownHostException + */ + public ServerAddress( String host , int port ) + throws UnknownHostException { + if ( host == null ) + host = defaultHost(); + host = host.trim(); + if ( host.length() == 0 ) + host = defaultHost(); + + int idx = host.indexOf( ":" ); + if ( idx > 0 ){ + if ( port != defaultPort() ) + throw new IllegalArgumentException( "can't specify port in construct and via host" ); + port = Integer.parseInt( host.substring( idx + 1 ) ); + host = host.substring( 0 , idx ).trim(); + } + + _host = host; + _port = port; + updateInetAddress(); + } + + /** + * Creates a ServerAddress with default port + * @param addr host address + */ + public ServerAddress( InetAddress addr ){ + this( new InetSocketAddress( addr , defaultPort() ) ); + } + + /** + * Creates a ServerAddress + * @param addr host address + * @param port mongod port + */ + public ServerAddress( InetAddress addr , int port ){ + this( new InetSocketAddress( addr , port ) ); + } + + /** + * Creates a ServerAddress + * @param addr inet socket address containing hostname and port + */ + public ServerAddress( InetSocketAddress addr ){ + _address = addr; + _host = _address.getHostName(); + _port = _address.getPort(); + } + + // -------- + // equality, etc... + // -------- + + + /** + * Determines whether this address is the same as a given host. + * @param host the address to compare + * @return if they are the same + */ + public boolean sameHost( String host ){ + int idx = host.indexOf( ":" ); + int port = defaultPort(); + if ( idx > 0 ){ + port = Integer.parseInt( host.substring( idx + 1 ) ); + host = host.substring( 0 , idx ); + } + + return + _port == port && + _host.equalsIgnoreCase( host ); + } + + @Override + public boolean equals( Object other ){ + if ( other instanceof ServerAddress ){ + ServerAddress a = (ServerAddress)other; + return + a._port == _port && + a._host.equals( _host ); + } + if ( other instanceof InetSocketAddress ){ + return _address.equals( other ); + } + return false; + } + + @Override + public int hashCode(){ + return _host.hashCode() + _port; + } + + /** + * Gets the hostname + * @return hostname + */ + public String getHost(){ + return _host; + } + + /** + * Gets the port number + * @return port + */ + public int getPort(){ + return _port; + } + + /** + * Gets the underlying socket address + * @return socket address + */ + public InetSocketAddress getSocketAddress(){ + return _address; + } + + @Override + public String toString(){ + return _address.toString(); + } + + final String _host; + final int _port; + volatile InetSocketAddress _address; + + // -------- + // static helpers + // -------- + + /** + * Returns the default database host: "127.0.0.1" + * @return IP address of default host. + */ + public static String defaultHost(){ + return "127.0.0.1"; + } + + /** Returns the default database port: 27017 + * @return the default port + */ + public static int defaultPort(){ + return DBPort.PORT; + } + + /** + * attempts to update the internal InetAddress by resolving the host name. + * @return true if host resolved to a new IP that is different from old one, false otherwise + * @throws UnknownHostException + */ + boolean updateInetAddress() throws UnknownHostException { + InetSocketAddress oldAddress = _address; + _address = new InetSocketAddress( InetAddress.getByName(_host) , _port ); + return !_address.equals(oldAddress); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/ServerError.java b/src/com/massivecraft/mcore3/lib/mongodb/ServerError.java new file mode 100644 index 00000000..c449909a --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/ServerError.java @@ -0,0 +1,103 @@ +// ServerError.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import com.massivecraft.mcore3.lib.bson.BSONObject; + +/** + * Represents a server error + */ +public class ServerError { + + ServerError( DBObject o ){ + _err = getMsg( o , null ); + if ( _err == null ) + throw new IllegalArgumentException( "need to have $err" ); + _code = getCode( o ); + } + + static String getMsg( BSONObject o , String def ){ + Object e = o.get( "$err" ); + if ( e == null ) + e = o.get( "err" ); + if ( e == null ) + e = o.get( "errmsg" ); + if ( e == null ) + return def; + return e.toString(); + } + + static int getCode( BSONObject o ){ + Object c = o.get( "code" ); + if ( c == null ) + c = o.get( "$code" ); + if ( c == null ) + c = o.get( "assertionCode" ); + + if ( c == null ) + return -5; + + return ((Number)c).intValue(); + } + + /** + * Gets the error String + * @return + */ + public String getError(){ + return _err; + } + + /** + * Gets the error code + * @return + */ + public int getCode(){ + return _code; + } + + /** + * returns true if the error is "not master", which usually happens when doing operation on slave + * @return + */ + public boolean isNotMasterError(){ + switch ( _code ){ + case 10054: + case 10056: + case 10058: + case 10107: + case 13435: + case 13436: + return true; + } + + return _err.startsWith( "not master" ); + } + + @Override + public String toString(){ + if ( _code > 0 ) + return _code + " " + _err; + return _err; + } + + final String _err; + final int _code; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/WriteConcern.java b/src/com/massivecraft/mcore3/lib/mongodb/WriteConcern.java new file mode 100644 index 00000000..283a309d --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/WriteConcern.java @@ -0,0 +1,453 @@ +// WriteConcern.java + +/** + * Copyright (C) 2008-2011 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +/** + *

    WriteConcern control the write behavior for with various options, as well as exception raising on error conditions.

    + * + *

    + * w + *

      + *
    • -1 = don't even report network errors
    • + *
    • 0 = default, don't call getLastError by default
    • + *
    • 1 = basic, call getLastError, but don't wait for slaves
    • + *
    • 2+= wait for slaves
    • + *
    + * wtimeout how long to wait for slaves before failing + *
      + *
    • 0 = indefinite
    • + *
    • > 0 = ms to wait
    • + *
    + *

    + *

    fsync force fsync to disk

    + * + * @dochub databases + */ +public class WriteConcern implements Serializable { + + private static final long serialVersionUID = 1884671104750417011L; + + /** No exceptions are raised, even for network issues */ + public final static WriteConcern NONE = new WriteConcern(-1); + + /** Exceptions are raised for network issues, but not server errors */ + public final static WriteConcern NORMAL = new WriteConcern(0); + + /** Exceptions are raised for network issues, and server errors; waits on a server for the write operation */ + public final static WriteConcern SAFE = new WriteConcern(1); + + /** Exceptions are raised for network issues, and server errors; waits on a majority of servers for the write operation */ + public final static WriteConcern MAJORITY = new Majority(); + + /** Exceptions are raised for network issues, and server errors; the write operation waits for the server to flush the data to disk*/ + public final static WriteConcern FSYNC_SAFE = new WriteConcern(true); + + /** Exceptions are raised for network issues, and server errors; the write operation waits for the server to group commit to the journal file on disk*/ + public final static WriteConcern JOURNAL_SAFE = new WriteConcern( 1, 0, false, true ); + + /** Exceptions are raised for network issues, and server errors; waits for at least 2 servers for the write operation*/ + public final static WriteConcern REPLICAS_SAFE = new WriteConcern(2); + + // map of the constants from above for use by fromString + private static Map _namedConcerns = null; + + /** + * Default constructor keeping all options as default + */ + public WriteConcern(){ + this(0); + } + + /** + * Calls {@link WriteConcern#WriteConcern(int, int, boolean)} with wtimeout=0 and fsync=false + * @param w number of writes + */ + public WriteConcern( int w ){ + this( w , 0 , false ); + } + + /** + * Tag based Write Concern with wtimeout=0, fsync=false, and j=false + * @param w Write Concern tag + */ + public WriteConcern( String w ){ + this( w , 0 , false, false ); + } + + /** + * Calls {@link WriteConcern#WriteConcern(int, int, boolean)} with fsync=false + * @param w number of writes + * @param wtimeout timeout for write operation + */ + public WriteConcern( int w , int wtimeout ){ + this( w , wtimeout , false ); + } + + /** + * Calls {@link WriteConcern#WriteConcern(int, int, boolean)} with w=1 and wtimeout=0 + * @param fsync whether or not to fsync + */ + public WriteConcern( boolean fsync ){ + this( 1 , 0 , fsync); + } + + /** + * Creates a WriteConcern object. + *

    Specifies the number of servers to wait for on the write operation, and exception raising behavior

    + *

    w represents the number of servers: + *

      + *
    • {@code w=-1} None, no checking is done
    • + *
    • {@code w=0} None, network socket errors raised
    • + *
    • {@code w=1} Checks server for errors as well as network socket errors raised
    • + *
    • {@code w>1} Checks servers (w) for errors as well as network socket errors raised
    • + *
    + *

    + * @param w number of writes + * @param wtimeout timeout for write operation + * @param fsync whether or not to fsync + */ + public WriteConcern( int w , int wtimeout , boolean fsync ){ + this(w, wtimeout, fsync, false); + } + + /** + * Creates a WriteConcern object. + *

    Specifies the number of servers to wait for on the write operation, and exception raising behavior

    + *

    w represents the number of servers: + *

      + *
    • {@code w=-1} None, no checking is done
    • + *
    • {@code w=0} None, network socket errors raised
    • + *
    • {@code w=1} Checks server for errors as well as network socket errors raised
    • + *
    • {@code w>1} Checks servers (w) for errors as well as network socket errors raised
    • + *
    + *

    + * @param w number of writes + * @param wtimeout timeout for write operation + * @param fsync whether or not to fsync + * @param j whether writes should wait for a journaling group commit + */ + public WriteConcern( int w , int wtimeout , boolean fsync , boolean j ){ + this( w, wtimeout, fsync, j, false); + } + + /** + * Creates a WriteConcern object. + *

    Specifies the number of servers to wait for on the write operation, and exception raising behavior

    + *

    w represents the number of servers: + *

      + *
    • {@code w=-1} None, no checking is done
    • + *
    • {@code w=0} None, network socket errors raised
    • + *
    • {@code w=1} Checks server for errors as well as network socket errors raised
    • + *
    • {@code w>1} Checks servers (w) for errors as well as network socket errors raised
    • + *
    + *

    + * @param w number of writes + * @param wtimeout timeout for write operation + * @param fsync whether or not to fsync + * @param j whether writes should wait for a journaling group commit + * @param continueOnInsertError if batch inserts should continue after the first error + */ + public WriteConcern( int w , int wtimeout , boolean fsync , boolean j, boolean continueOnInsertError) { + _w = w; + _wtimeout = wtimeout; + _fsync = fsync; + _j = j; + _continueOnErrorForInsert = continueOnInsertError; + } + + /** + * Creates a WriteConcern object. + *

    Specifies the number of servers to wait for on the write operation, and exception raising behavior

    + *

    w represents the number of servers: + *

      + *
    • {@code w=-1} None, no checking is done
    • + *
    • {@code w=0} None, network socket errors raised
    • + *
    • {@code w=1} Checks server for errors as well as network socket errors raised
    • + *
    • {@code w>1} Checks servers (w) for errors as well as network socket errors raised
    • + *
    + *

    + * @param w number of writes + * @param wtimeout timeout for write operation + * @param fsync whether or not to fsync + * @param j whether writes should wait for a journaling group commit + */ + public WriteConcern( String w , int wtimeout , boolean fsync, boolean j ){ + this( w, wtimeout, fsync, j, false); + } + + /** + * Creates a WriteConcern object. + *

    Specifies the number of servers to wait for on the write operation, and exception raising behavior

    + *

    w represents the number of servers: + *

      + *
    • {@code w=-1} None, no checking is done
    • + *
    • {@code w=0} None, network socket errors raised
    • + *
    • {@code w=1} Checks server for errors as well as network socket errors raised
    • + *
    • {@code w>1} Checks servers (w) for errors as well as network socket errors raised
    • + *
    + *

    + * @param w number of writes + * @param wtimeout timeout for write operation + * @param fsync whether or not to fsync + * @param j whether writes should wait for a journaling group commit + * @param continueOnInsertError if batch inserts should continue after the first error + * @return + */ + public WriteConcern( String w , int wtimeout , boolean fsync, boolean j, boolean continueOnInsertError ){ + if (w == null) { + throw new IllegalArgumentException("w can not be null"); + } + + _w = w; + _wtimeout = wtimeout; + _fsync = fsync; + _j = j; + _continueOnErrorForInsert = continueOnInsertError; + } + + public BasicDBObject getCommand(){ + BasicDBObject _command = new BasicDBObject( "getlasterror" , 1 ); + + if ( _w instanceof Integer && ( (Integer) _w > 0) || + ( _w instanceof String && _w != null ) ){ + _command.put( "w" , _w ); + _command.put( "wtimeout" , _wtimeout ); + } + + if ( _fsync ) + _command.put( "fsync" , true ); + + if ( _j ) + _command.put( "j", true ); + + return _command; + } + + /** + * Sets the w value (the write strategy) + * @param w + */ + public void setWObject(Object w) { + if ( ! (w instanceof Integer) && ! (w instanceof String) ) + throw new IllegalArgumentException("The w parameter must be an int or a String"); + this._w = w; + } + + /** + * Gets the w value (the write strategy) + * @return + */ + public Object getWObject(){ + return _w; + } + + /** + * Gets the w parameter (the write strategy) + * @return + */ + public int getW(){ + return (Integer) _w; + } + + /** + * Gets the w parameter (the write strategy) in String format + * @return + */ + public String getWString(){ + return _w.toString(); + } + + /** + * Gets the write timeout (in milliseconds) + * @return + */ + public int getWtimeout(){ + return _wtimeout; + } + + /** + * Gets the fsync flag (fsync to disk on the server) + * @return + */ + public boolean getFsync(){ + return _fsync; + } + + /** + * Gets the fsync flag (fsync to disk on the server) + * @return + */ + public boolean fsync(){ + return _fsync; + } + + /** + * Returns whether network error may be raised (w >= 0) + * @return + */ + public boolean raiseNetworkErrors(){ + if (_w instanceof Integer) + return (Integer) _w >= 0; + return _w != null; + } + + /** + * Returns whether "getlasterror" should be called (w > 0) + * @return + */ + public boolean callGetLastError(){ + if (_w instanceof Integer) + return (Integer) _w > 0; + return _w != null; + } + + /** + * Gets the WriteConcern constants by name: NONE, NORMAL, SAFE, FSYNC_SAFE, + * REPLICA_SAFE. (matching is done case insensitively) + * @param name + * @return + */ + public static WriteConcern valueOf(String name) { + if (_namedConcerns == null) { + HashMap newMap = new HashMap( 8 , 1 ); + for (Field f : WriteConcern.class.getFields()) + if (Modifier.isStatic( f.getModifiers() ) && f.getType().equals( WriteConcern.class )) { + try { + newMap.put( f.getName().toLowerCase(), (WriteConcern) f.get( null ) ); + } catch (Exception e) { + throw new RuntimeException( e ); + } + } + + // Thought about doing a synchronize but this seems just as safe and + // I don't care about race conditions. + _namedConcerns = newMap; + } + + return _namedConcerns.get( name.toLowerCase() ); + } + + @Override + public String toString(){ + return "WriteConcern " + getCommand() + " / (Continue Inserting on Errors? " + getContinueOnErrorForInsert() + ")"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WriteConcern that = (WriteConcern) o; + + if (_continueOnErrorForInsert != that._continueOnErrorForInsert) return false; + if (_fsync != that._fsync) return false; + if (_j != that._j) return false; + if (_wtimeout != that._wtimeout) return false; + if (!_w.equals(that._w)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = _w.hashCode(); + result = 31 * result + _wtimeout; + result = 31 * result + (_fsync ? 1 : 0); + result = 31 * result + (_j ? 1 : 0); + result = 31 * result + (_continueOnErrorForInsert ? 1 : 0); + return result; + } + + /** + * Gets the j parameter (journal syncing) + * @return + */ + public boolean getJ() { + return _j; + } + + /** + * Toggles the "continue inserts on error" mode. This only applies to server side errors. + * If there is a document which does not validate in the client, an exception will still + * be thrown in the client. + * This will return a *NEW INSTANCE* of WriteConcern with your preferred continueOnInsert value + * + * @param continueOnErrorForInsert + */ + public WriteConcern continueOnErrorForInsert(boolean continueOnErrorForInsert) { + if ( _w instanceof Integer ) + return new WriteConcern((Integer) _w, _wtimeout, _fsync, _j, continueOnErrorForInsert); + else if ( _w instanceof String ) + return new WriteConcern((String) _w, _wtimeout, _fsync, _j, continueOnErrorForInsert); + else + throw new IllegalStateException("The w parameter must be an int or a String"); + } + + /** + * Gets the "continue inserts on error" mode + * @return + */ + public boolean getContinueOnErrorForInsert() { + return _continueOnErrorForInsert; + } + + /** + * Create a Majority Write Concern that requires a majority of + * servers to acknowledge the write. + * + * @param wtimeout timeout for write operation + * @param fsync whether or not to fsync + * @param j whether writes should wait for a journaling group commit + */ + public static Majority majorityWriteConcern( int wtimeout, boolean fsync, boolean j ) { + return new Majority( wtimeout, fsync, j ); + } + + + Object _w = 0; + int _wtimeout = 0; + boolean _fsync = false; + boolean _j = false; + boolean _continueOnErrorForInsert = false; + + public static class Majority extends WriteConcern { + + private static final long serialVersionUID = -4128295115883875212L; + + public Majority( ) { + super( "majority", 0, false, false ); + } + + public Majority( int wtimeout, boolean fsync, boolean j ){ + super( "majority", wtimeout, fsync, j ); + } + + @Override + public String toString(){ + return "[Majority] WriteConcern " + getCommand(); + } + + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/WriteResult.java b/src/com/massivecraft/mcore3/lib/mongodb/WriteResult.java new file mode 100644 index 00000000..74e56f58 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/WriteResult.java @@ -0,0 +1,161 @@ +// WriteResult.java + +package com.massivecraft.mcore3.lib.mongodb; + +import java.io.IOException; + + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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. + */ + + +/** + * This class lets you access the results of the previous write. + * if you have STRICT mode on, this just stores the result of that getLastError call + * if you don't, then this will actually do the getlasterror call. + * if another operation has been done on this connection in the interim, calls will fail + */ +public class WriteResult { + + WriteResult( CommandResult o , WriteConcern concern ){ + _lastErrorResult = o; + _lastConcern = concern; + _lazy = false; + _port = null; + _db = null; + } + + WriteResult( DB db , DBPort p , WriteConcern concern ){ + _db = db; + _port = p; + _lastCall = p._calls; + _lastConcern = concern; + _lazy = true; + } + + /** + * Gets the last result from getLastError() + * @return + */ + public CommandResult getCachedLastError(){ + return _lastErrorResult; + + } + + /** + * Gets the last {@link WriteConcern} used when calling getLastError() + * @return + */ + public WriteConcern getLastConcern(){ + return _lastConcern; + + } + + /** + * calls {@link WriteResult#getLastError(com.mongodb.WriteConcern)} with concern=null + * @return + */ + public synchronized CommandResult getLastError(){ + return getLastError(null); + } + + /** + * This method does following: + * - returns the existing CommandResult if concern is null or less strict than the concern it was obtained with + * - otherwise attempts to obtain a CommandResult by calling getLastError with the concern + * @param concern the concern + * @return + */ + public synchronized CommandResult getLastError(WriteConcern concern){ + if ( _lastErrorResult != null ) { + // do we have a satisfying concern? + if ( concern == null || ( _lastConcern != null && _lastConcern.getW() >= concern.getW() ) ) + return _lastErrorResult; + } + + // here we dont have a satisfying result + if ( _port != null ){ + try { + _lastErrorResult = _port.tryGetLastError( _db , _lastCall , (concern == null) ? new WriteConcern() : concern ); + } catch ( IOException ioe ){ + throw new MongoException.Network( ioe.getMessage() , ioe ); + } + + if (_lastErrorResult == null) + throw new IllegalStateException( "The connection may have been used since this write, cannot obtain a result" ); + _lastConcern = concern; + _lastCall++; + } else { + // this means we dont have satisfying result and cant get new one + throw new IllegalStateException( "Don't have a port to obtain a write result, and existing one is not good enough." ); + } + + return _lastErrorResult; + } + + + /** + * Gets the error String ("err" field) + * @return + */ + public String getError(){ + Object foo = getField( "err" ); + if ( foo == null ) + return null; + return foo.toString(); + } + + /** + * Gets the "n" field, which contains the number of documents + * affected in the write operation. + * @return + */ + public int getN(){ + return getLastError().getInt( "n" ); + } + + /** + * Gets a field + * @param name field name + * @return + */ + public Object getField( String name ){ + return getLastError().get( name ); + } + + /** + * Returns whether or not the result is lazy, meaning that getLastError was not called automatically + * @return + */ + public boolean isLazy(){ + return _lazy; + } + + @Override + public String toString(){ + CommandResult res = getCachedLastError(); + if (res != null) + return res.toString(); + return "N/A"; + } + + private long _lastCall; + private WriteConcern _lastConcern; + private CommandResult _lastErrorResult; + final private DB _db; + final private DBPort _port; + final private boolean _lazy; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/gridfs/CLI.java b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/CLI.java new file mode 100644 index 00000000..93b2f2a4 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/CLI.java @@ -0,0 +1,162 @@ +// CLI.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.gridfs; + +import java.io.File; +import java.security.DigestInputStream; +import java.security.MessageDigest; + +import com.massivecraft.mcore3.lib.mongodb.DBObject; +import com.massivecraft.mcore3.lib.mongodb.Mongo; +import com.massivecraft.mcore3.lib.mongodb.util.Util; + + +/** + * a simple CLI for Gridfs + */ +public class CLI { + + /** + * Dumps usage info to stdout + */ + private static void printUsage() { + System.out.println("Usage : [--bucket bucketname] action"); + System.out.println(" where action is one of:"); + System.out.println(" list : lists all files in the store"); + System.out.println(" put filename : puts the file filename into the store"); + System.out.println(" get filename1 filename2 : gets filename1 from store and sends to filename2"); + System.out.println(" md5 filename : does an md5 hash on a file in the db (for testing)"); + } + + private static String host = "127.0.0.1"; + private static String db = "test"; + + private static Mongo _mongo = null; + private static Mongo getMongo() + throws Exception { + if ( _mongo == null ) + _mongo = new Mongo( host ); + return _mongo; + } + + private static GridFS _gridfs; + private static GridFS getGridFS() + throws Exception { + if ( _gridfs == null ) + _gridfs = new GridFS( getMongo().getDB( db ) ); + return _gridfs; + } + + @SuppressWarnings("unused") + public static void main(String[] args) throws Exception { + + if ( args.length < 1 ){ + printUsage(); + return; + } + + Mongo m = null; + + for ( int i=0; i= 0 ){ + read++; + int r = is.read( new byte[17] ); + if ( r < 0 ) + break; + read += r; + } + byte[] digest = md5.digest(); + System.out.println( "length: " + read + " md5: " + Util.toHex( digest ) ); + return; + } + + + System.err.println( "unknown option: " + s ); + return; + } + + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFS.java b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFS.java new file mode 100644 index 00000000..b410da3e --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFS.java @@ -0,0 +1,355 @@ +// GridFS.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.gridfs; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; +import com.massivecraft.mcore3.lib.mongodb.BasicDBObject; +import com.massivecraft.mcore3.lib.mongodb.BasicDBObjectBuilder; +import com.massivecraft.mcore3.lib.mongodb.DB; +import com.massivecraft.mcore3.lib.mongodb.DBCollection; +import com.massivecraft.mcore3.lib.mongodb.DBCursor; +import com.massivecraft.mcore3.lib.mongodb.DBObject; + +/** + * Implementation of GridFS v1.0 + * + * GridFS 1.0 spec + * + * @dochub gridfs + */ +public class GridFS { + + /** + * file's chunk size + */ + public static final int DEFAULT_CHUNKSIZE = 256 * 1024; + + /** + * file's max chunk size + */ + public static final long MAX_CHUNKSIZE = (long)(3.5 * 1000 * 1000); + + /** + * bucket to use for the collection namespaces + */ + public static final String DEFAULT_BUCKET = "fs"; + + // -------------------------- + // ------ constructors ------- + // -------------------------- + + /** + * Creates a GridFS instance for the default bucket "fs" + * in the given database. Set the preferred WriteConcern on the give DB with DB.setWriteConcern + * @see com.massivecraft.mcore3.lib.mongodb.WriteConcern + * @param db database to work with + */ + public GridFS(DB db) { + this(db, DEFAULT_BUCKET); + } + + /** + * Creates a GridFS instance for the specified bucket + * in the given database. Set the preferred WriteConcern on the give DB with DB.setWriteConcern + * + * @see com.massivecraft.mcore3.lib.mongodb.WriteConcern + * @param db database to work with + * @param bucket bucket to use in the given database + */ + public GridFS(DB db, String bucket) { + _db = db; + _bucketName = bucket; + + _filesCollection = _db.getCollection( _bucketName + ".files" ); + _chunkCollection = _db.getCollection( _bucketName + ".chunks" ); + + // ensure standard indexes as long as collections are small + if (_filesCollection.count() < 1000) + _filesCollection.ensureIndex( BasicDBObjectBuilder.start().add( "filename" , 1 ).add( "uploadDate" , 1 ).get() ); + if (_chunkCollection.count() < 1000) + _chunkCollection.ensureIndex( BasicDBObjectBuilder.start().add( "files_id" , 1 ).add( "n" , 1 ).get() , BasicDBObjectBuilder.start().add( "unique" , 1 ).get() ); + + _filesCollection.setObjectClass( GridFSDBFile.class ); + } + + + // -------------------------- + // ------ utils ------- + // -------------------------- + + + /** + * gets the list of files stored in this gridfs, sorted by filename + * + * @return cursor of file objects + */ + public DBCursor getFileList(){ + return _filesCollection.find().sort(new BasicDBObject("filename",1)); + } + + /** + * gets a filtered list of files stored in this gridfs, sorted by filename + * + * @param query filter to apply + * @return cursor of file objects + */ + public DBCursor getFileList( DBObject query ){ + return _filesCollection.find( query ).sort(new BasicDBObject("filename",1)); + } + + + // -------------------------- + // ------ reading ------- + // -------------------------- + + /** + * finds one file matching the given id. Equivalent to findOne(id) + * @param id + * @return + */ + public GridFSDBFile find( ObjectId id ){ + return findOne( id ); + } + /** + * finds one file matching the given id. + * @param id + * @return + */ + public GridFSDBFile findOne( ObjectId id ){ + return findOne( new BasicDBObject( "_id" , id ) ); + } + /** + * finds one file matching the given filename + * @param filename + * @return + */ + public GridFSDBFile findOne( String filename ){ + return findOne( new BasicDBObject( "filename" , filename ) ); + } + /** + * finds one file matching the given query + * @param query + * @return + */ + public GridFSDBFile findOne( DBObject query ){ + return _fix( _filesCollection.findOne( query ) ); + } + + /** + * finds a list of files matching the given filename + * @param filename + * @return + */ + public List find( String filename ){ + return find( new BasicDBObject( "filename" , filename ) ); + } + /** + * finds a list of files matching the given query + * @param query + * @return + */ + public List find( DBObject query ){ + List files = new ArrayList(); + + DBCursor c = _filesCollection.find( query ); + while ( c.hasNext() ){ + files.add( _fix( c.next() ) ); + } + return files; + } + + private GridFSDBFile _fix( Object o ){ + if ( o == null ) + return null; + + if ( ! ( o instanceof GridFSDBFile ) ) + throw new RuntimeException( "somehow didn't get a GridFSDBFile" ); + + GridFSDBFile f = (GridFSDBFile)o; + f._fs = this; + return f; + } + + + // -------------------------- + // ------ remove ------- + // -------------------------- + + /** + * removes the file matching the given id + * @param id + */ + public void remove( ObjectId id ){ + _filesCollection.remove( new BasicDBObject( "_id" , id ) ); + _chunkCollection.remove( new BasicDBObject( "files_id" , id ) ); + } + + /** + * removes all files matching the given filename + * @param filename + */ + public void remove( String filename ){ + remove( new BasicDBObject( "filename" , filename ) ); + } + + /** + * removes all files matching the given query + * @param query + */ + public void remove( DBObject query ){ + for ( GridFSDBFile f : find( query ) ){ + f.remove(); + } + } + + + // -------------------------- + // ------ writing ------- + // -------------------------- + + /** + * creates a file entry. + * After calling this method, you have to call save() on the GridFSInputFile file + * @param data the file's data + * @return + */ + public GridFSInputFile createFile( byte[] data ){ + return createFile( new ByteArrayInputStream( data ), true ); + } + + + /** + * creates a file entry. + * After calling this method, you have to call save() on the GridFSInputFile file + * @param f the file object + * @return + * @throws IOException + */ + public GridFSInputFile createFile( File f ) + throws IOException { + return createFile( new FileInputStream( f ) , f.getName(), true ); + } + + /** + * creates a file entry. + * after calling this method, you have to call save() on the GridFSInputFile file + * @param in an inputstream containing the file's data + * @return + */ + public GridFSInputFile createFile( InputStream in ){ + return createFile( in , null ); + } + + /** + * creates a file entry. + * after calling this method, you have to call save() on the GridFSInputFile file + * @param in an inputstream containing the file's data + * @param closeStreamOnPersist indicate the passed in input stream should be closed + * once the data chunk persisted + * @return + */ + public GridFSInputFile createFile( InputStream in, boolean closeStreamOnPersist ){ + return createFile( in , null, closeStreamOnPersist ); + } + + /** + * creates a file entry. + * After calling this method, you have to call save() on the GridFSInputFile file + * @param in an inputstream containing the file's data + * @param filename the file name as stored in the db + * @return + */ + public GridFSInputFile createFile( InputStream in , String filename ){ + return new GridFSInputFile( this , in , filename ); + } + + /** + * creates a file entry. + * After calling this method, you have to call save() on the GridFSInputFile file + * @param in an inputstream containing the file's data + * @param filename the file name as stored in the db + * @param closeStreamOnPersist indicate the passed in input stream should be closed + * once the data chunk persisted + * @return + */ + public GridFSInputFile createFile( InputStream in , String filename, boolean closeStreamOnPersist ){ + return new GridFSInputFile( this , in , filename, closeStreamOnPersist ); + } + + /** + * @see {@link GridFS#createFile()} on how to use this method + * @param filename the file name as stored in the db + * @return + */ + public GridFSInputFile createFile(String filename) { + return new GridFSInputFile( this , filename ); + } + + /** + * This method creates an empty {@link GridFSInputFile} instance. On this + * instance an {@link java.io.OutputStream} can be obtained using the + * {@link GridFSInputFile#getOutputStream()} method. You can still call + * {@link GridFSInputFile#setContentType(String)} and + * {@link GridFSInputFile#setFilename(String)}. The file will be completely + * written and closed after calling the {@link java.io.OutputStream#close()} + * method on the output stream. + * + * @return GridFS file handle instance. + */ + public GridFSInputFile createFile() { + return new GridFSInputFile( this ); + } + + + + // -------------------------- + // ------ members ------- + // -------------------------- + + /** + * gets the bucket name used in the collection's namespace + * @return + */ + public String getBucketName(){ + return _bucketName; + } + + /** + * gets the db used + * @return + */ + public DB getDB(){ + return _db; + } + + protected final DB _db; + protected final String _bucketName; + protected final DBCollection _filesCollection; + protected final DBCollection _chunkCollection; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSDBFile.java b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSDBFile.java new file mode 100644 index 00000000..1e58ba88 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSDBFile.java @@ -0,0 +1,200 @@ +// GridFSDBFile.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.gridfs; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.massivecraft.mcore3.lib.mongodb.BasicDBObject; +import com.massivecraft.mcore3.lib.mongodb.BasicDBObjectBuilder; +import com.massivecraft.mcore3.lib.mongodb.DBObject; +import com.massivecraft.mcore3.lib.mongodb.MongoException; + +/** + * This class enables to retrieve a GridFS file metadata and content. + * Operations include: + * - writing data to a file on disk or an OutputStream + * - getting each chunk as a byte array + * - getting an InputStream to stream the data into + * @author antoine + */ +public class GridFSDBFile extends GridFSFile { + + + /** + * Returns an InputStream from which data can be read + * @return + */ + public InputStream getInputStream(){ + return new MyInputStream(); + } + + /** + * Writes the file's data to a file on disk + * @param filename the file name on disk + * @return + * @throws IOException + */ + public long writeTo( String filename ) throws IOException { + return writeTo( new File( filename ) ); + } + /** + * Writes the file's data to a file on disk + * @param f the File object + * @return + * @throws IOException + */ + public long writeTo( File f ) throws IOException { + + FileOutputStream out = null; + try{ + out = new FileOutputStream( f ); + return writeTo( out); + }finally{ + if(out != null) + out.close(); + } + } + + /** + * Writes the file's data to an OutputStream + * @param out the OutputStream + * @return + * @throws IOException + */ + public long writeTo( OutputStream out ) + throws IOException { + final int nc = numChunks(); + for ( int i=0; i= _data.length ){ + if ( _currentChunkIdx + 1 >= _numChunks ) + return -1; + + _data = getChunk( ++_currentChunkIdx ); + _offset = 0; + } + + int r = Math.min( len , _data.length - _offset ); + System.arraycopy( _data , _offset , b , off , r ); + _offset += r; + return r; + } + + /** + * Will smartly skips over chunks without fetching them if possible. + */ + public long skip(long numBytesToSkip) throws IOException { + if (numBytesToSkip <= 0) + return 0; + + if (_currentChunkIdx == _numChunks) + //We're actually skipping over the back end of the file, short-circuit here + //Don't count those extra bytes to skip in with the return value + return 0; + + // offset in the whole file + long offsetInFile = 0; + if (_currentChunkIdx >= 0) + offsetInFile = _currentChunkIdx * _chunkSize + _offset; + if (numBytesToSkip + offsetInFile >= _length) { + _currentChunkIdx = _numChunks; + _data = null; + return _length - offsetInFile; + } + + int temp = _currentChunkIdx; + _currentChunkIdx = (int)((numBytesToSkip + offsetInFile) / _chunkSize); + if (temp != _currentChunkIdx) + _data = getChunk(_currentChunkIdx); + _offset = (int)((numBytesToSkip + offsetInFile) % _chunkSize); + + return numBytesToSkip; + } + + final int _numChunks; + + int _currentChunkIdx = -1; + int _offset = 0; + byte[] _data = null; + } + + void remove(){ + _fs._filesCollection.remove( new BasicDBObject( "_id" , _id ) ); + _fs._chunkCollection.remove( new BasicDBObject( "files_id" , _id ) ); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSFile.java b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSFile.java new file mode 100644 index 00000000..271f336f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSFile.java @@ -0,0 +1,305 @@ +// GridFSFile.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.gridfs; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.mongodb.BasicDBObject; +import com.massivecraft.mcore3.lib.mongodb.DBObject; +import com.massivecraft.mcore3.lib.mongodb.MongoException; +import com.massivecraft.mcore3.lib.mongodb.util.JSON; + +/** + * The abstract class representing a GridFS file + * @author antoine + */ +public abstract class GridFSFile implements DBObject { + + + // ------------------------------ + // --------- db ------- + // ------------------------------ + + /** + * Saves the file entry to the files collection + */ + public void save(){ + if ( _fs == null ) + throw new MongoException( "need _fs" ); + _fs._filesCollection.save( this ); + } + + /** + * Verifies that the MD5 matches between the database and the local file. + * This should be called after transferring a file. + * @throws MongoException + */ + public void validate() throws MongoException { + if ( _fs == null ) + throw new MongoException( "no _fs" ); + if ( _md5 == null ) + throw new MongoException( "no _md5 stored" ); + + DBObject cmd = new BasicDBObject( "filemd5" , _id ); + cmd.put( "root" , _fs._bucketName ); + DBObject res = _fs._db.command( cmd ); + if ( res != null && res.containsField( "md5" ) ) { + String m = res.get( "md5" ).toString(); + if ( m.equals( _md5 ) ) + return; + throw new MongoException( "md5 differ. mine [" + _md5 + "] theirs [" + m + "]" ); + } + + // no md5 from the server + throw new MongoException( "no md5 returned from server: " + res ); + + } + + /** + * Returns the number of chunks that store the file data + * @return + */ + public int numChunks(){ + double d = _length; + d = d / _chunkSize; + return (int)Math.ceil( d ); + } + + // ------------------------------ + // --------- getters ------- + // ------------------------------ + + + /** + * Gets the id + * @return + */ + public Object getId(){ + return _id; + } + + /** + * Gets the filename + * @return + */ + public String getFilename(){ + return _filename; + } + + /** + * Gets the content type + * @return + */ + public String getContentType(){ + return _contentType; + } + + /** + * Gets the file's length + * @return + */ + public long getLength(){ + return _length; + } + + /** + * Gets the size of a chunk + * @return + */ + public long getChunkSize(){ + return _chunkSize; + } + + /** + * Gets the upload date + * @return + */ + public Date getUploadDate(){ + return _uploadDate; + } + + /** + * Gets the aliases from the metadata. + * note: to set aliases, call put( "aliases" , List ) + * @return + */ + @SuppressWarnings("unchecked") + public List getAliases(){ + return (List)_extradata.get( "aliases" ); + } + + /** + * Gets the file metadata + * @return + */ + public DBObject getMetaData(){ + return (DBObject)_extradata.get( "metadata" ); + } + + /** + * Gets the file metadata + * @return + */ + public void setMetaData(DBObject metadata){ + _extradata.put( "metadata", metadata ); + } + + /** + * Gets the observed MD5 during transfer + * @return + */ + public String getMD5(){ + return _md5; + } + + // ------------------------------ + // --------- DBOBject methods --- + // ------------------------------ + + public Object put( String key , Object v ){ + if ( key == null ) + throw new RuntimeException( "key should never be null" ); + else if ( key.equals( "_id" ) ) + _id = v; + else if ( key.equals( "filename" ) ) + _filename = v == null ? null : v.toString(); + else if ( key.equals( "contentType" ) ) + _contentType = (String)v; + else if ( key.equals( "length" ) ) + _length = ((Number)v).longValue(); + else if ( key.equals( "chunkSize" ) ) + _chunkSize = ((Number)v).longValue(); + else if ( key.equals( "uploadDate" ) ) + _uploadDate = (Date)v; + else if ( key.equals( "md5" ) ) + _md5 = (String)v; + else + _extradata.put( key , v ); + return v; + } + + public Object get( String key ){ + if ( key == null ) + throw new RuntimeException( "key should never be null" ); + else if ( key.equals( "_id" ) ) + return _id; + else if ( key.equals( "filename" ) ) + return _filename; + else if ( key.equals( "contentType" ) ) + return _contentType; + else if ( key.equals( "length" ) ) + return _length; + else if ( key.equals( "chunkSize" ) ) + return _chunkSize; + else if ( key.equals( "uploadDate" ) ) + return _uploadDate; + else if ( key.equals( "md5" ) ) + return _md5; + return _extradata.get( key ); + } + + public void putAll( BSONObject o ){ + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("rawtypes") + public void putAll( Map m ){ + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("rawtypes") + public Map toMap(){ + throw new UnsupportedOperationException(); + } + + public Object removeField( String key ){ + throw new UnsupportedOperationException(); + } + + /* + * @deprecated + */ + @Deprecated + public boolean containsKey( String s ){ + return containsField( s ); + } + + public boolean containsField(String s){ + return keySet().contains( s ); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Set keySet(){ + Set keys = new HashSet(); + keys.addAll(VALID_FIELDS); + keys.addAll(_extradata.keySet()); + return keys; + } + + public boolean isPartialObject(){ + return false; + } + + public void markAsPartialObject(){ + throw new RuntimeException( "can't load partial GridFSFile file" ); + } + + // ---------------------- + // ------- fields ------- + // ---------------------- + + @Override + public String toString(){ + return JSON.serialize( this ); + } + + /** + * Sets the GridFS associated with this file + * @param fs + */ + protected void setGridFS( GridFS fs ){ + _fs = fs; + } + + protected GridFS _fs = null; + + Object _id; + String _filename; + String _contentType; + long _length; + long _chunkSize; + Date _uploadDate; + List _aliases; + DBObject _extradata = new BasicDBObject(); + String _md5; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + final static Set VALID_FIELDS = Collections.unmodifiableSet( new HashSet( Arrays.asList( new String[]{ + "_id" , "filename" , "contentType" , "length" , "chunkSize" , + "uploadDate" , "aliases" , "md5" + } ) ) ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSInputFile.java b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSInputFile.java new file mode 100644 index 00000000..836b4aa2 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSInputFile.java @@ -0,0 +1,419 @@ +// GridFSInputFile.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.gridfs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.Date; + + +import com.massivecraft.mcore3.lib.bson.types.ObjectId; +import com.massivecraft.mcore3.lib.mongodb.BasicDBObjectBuilder; +import com.massivecraft.mcore3.lib.mongodb.DBObject; +import com.massivecraft.mcore3.lib.mongodb.MongoException; +import com.massivecraft.mcore3.lib.mongodb.util.SimplePool; +import com.massivecraft.mcore3.lib.mongodb.util.Util; + +/** + * This class represents a GridFS file to be written to the database + * Operations include: + * - writing data obtained from an InputStream + * - getting an OutputStream to stream the data out + * + * @author Eliot Horowitz and Guy K. Kloss + */ +public class GridFSInputFile extends GridFSFile { + + /** + * Default constructor setting the GridFS file name and providing an input + * stream containing data to be written to the file. + * + * @param fs + * The GridFS connection handle. + * @param in + * Stream used for reading data from. + * @param filename + * Name of the file to be created. + * @param closeStreamOnPersist + indicate the passed in input stream should be closed once the data chunk persisted + */ + protected GridFSInputFile( GridFS fs , InputStream in , String filename, boolean closeStreamOnPersist ) { + _fs = fs; + _in = in; + _filename = filename; + _closeStreamOnPersist = closeStreamOnPersist; + + _id = new ObjectId(); + _chunkSize = GridFS.DEFAULT_CHUNKSIZE; + _uploadDate = new Date(); + _messageDigester = _md5Pool.get(); + _messageDigester.reset(); + _buffer = new byte[(int) _chunkSize]; + } + + /** + * Default constructor setting the GridFS file name and providing an input + * stream containing data to be written to the file. + * + * @param fs + * The GridFS connection handle. + * @param in + * Stream used for reading data from. + * @param filename + * Name of the file to be created. + */ + protected GridFSInputFile( GridFS fs , InputStream in , String filename ) { + this( fs, in, filename, false); + } + + /** + * Constructor that only provides a file name, but does not rely on the + * presence of an {@link java.io.InputStream}. An + * {@link java.io.OutputStream} can later be obtained for writing using the + * {@link #getOutputStream()} method. + * + * @param fs + * The GridFS connection handle. + * @param filename + * Name of the file to be created. + */ + protected GridFSInputFile( GridFS fs , String filename ) { + this( fs , null , filename ); + } + + /** + * Minimal constructor that does not rely on the presence of an + * {@link java.io.InputStream}. An {@link java.io.OutputStream} can later be + * obtained for writing using the {@link #getOutputStream()} method. + * + * @param fs + * The GridFS connection handle. + */ + protected GridFSInputFile( GridFS fs ) { + this( fs , null , null ); + } + + public void setId(Object id) { + _id = id; + } + + /** + * Sets the file name on the GridFS entry. + * + * @param fn + * File name. + */ + public void setFilename( String fn ) { + _filename = fn; + } + + /** + * Sets the content type (MIME type) on the GridFS entry. + * + * @param ct + * Content type. + */ + public void setContentType( String ct ) { + _contentType = ct; + } + + /** + * Set the chunk size. This must be called before saving any data. + * @param chunkSize The size in bytes. + */ + public void setChunkSize(long chunkSize) { + if (_outputStream != null || _savedChunks) + return; + _chunkSize = chunkSize; + _buffer = new byte[(int) _chunkSize]; + } + + /** + * calls {@link GridFSInputFile#save(long)} with the existing chunk size + */ + @Override + public void save() { + save( _chunkSize ); + } + + /** + * This method first calls saveChunks(long) if the file data has not been saved yet. + * Then it persists the file entry to GridFS. + * + * @param chunkSize + * Size of chunks for file in bytes. + */ + public void save( long chunkSize ) { + if (_outputStream != null) + throw new MongoException( "cannot mix OutputStream and regular save()" ); + + // note that chunkSize only changes _chunkSize in case we actually save chunks + // otherwise there is a risk file and chunks are not compatible + if ( ! _savedChunks ) { + try { + saveChunks( chunkSize ); + } catch ( IOException ioe ) { + throw new MongoException( "couldn't save chunks" , ioe ); + } + } + + super.save(); + } + + /** + * @see com.massivecraft.mcore3.lib.mongodb.gridfs.GridFSInputFile#saveChunks(long) + * + * @return Number of the next chunk. + * @throws IOException + * on problems reading the new entry's + * {@link java.io.InputStream}. + */ + public int saveChunks() throws IOException { + return saveChunks( _chunkSize ); + } + + /** + * Saves all data into chunks from configured {@link java.io.InputStream} input stream + * to GridFS. A non-default chunk size can be specified. + * This method does NOT save the file object itself, one must call save() to do so. + * + * @param chunkSize + * Size of chunks for file in bytes. + * @return Number of the next chunk. + * @throws IOException + * on problems reading the new entry's + * {@link java.io.InputStream}. + */ + public int saveChunks( long chunkSize ) throws IOException { + if (_outputStream != null) + throw new MongoException( "cannot mix OutputStream and regular save()" ); + if ( _savedChunks ) + throw new MongoException( "chunks already saved!" ); + + if ( chunkSize <= 0 || chunkSize > GridFS.MAX_CHUNKSIZE ) { + throw new MongoException( "chunkSize must be greater than zero and less than or equal to GridFS.MAX_CHUNKSIZE"); + } + + if ( _chunkSize != chunkSize ) { + _chunkSize = chunkSize; + _buffer = new byte[(int) _chunkSize]; + } + + int bytesRead = 0; + while ( bytesRead >= 0 ) { + _currentBufferPosition = 0; + bytesRead = _readStream2Buffer(); + _dumpBuffer( true ); + } + + // only finish data, do not write file, in case one wants to change metadata + _finishData(); + return _currentChunkNumber; + } + + /** + * After retrieving this {@link java.io.OutputStream}, this object will be + * capable of accepting successively written data to the output stream. + * To completely persist this GridFS object, you must finally call the {@link java.io.OutputStream#close()} + * method on the output stream. Note that calling the save() and saveChunks() + * methods will throw Exceptions once you obtained the OutputStream. + * + * @return Writable stream object. + */ + public OutputStream getOutputStream() { + if ( _outputStream == null ) { + _outputStream = new MyOutputStream(); + } + return _outputStream; + } + + /** + * Dumps a new chunk into the chunks collection. Depending on the flag, also + * partial buffers (at the end) are going to be written immediately. + * + * @param data + * Data for chunk. + * @param writePartial + * Write also partial buffers full. + */ + private void _dumpBuffer( boolean writePartial ) { + if ( ( _currentBufferPosition < _chunkSize ) && !writePartial ) { + // Bail out, chunk not complete yet + return; + } + if (_currentBufferPosition == 0) { + // chunk is empty, may be last chunk + return; + } + + byte[] writeBuffer = _buffer; + if ( _currentBufferPosition != _chunkSize ) { + writeBuffer = new byte[_currentBufferPosition]; + System.arraycopy( _buffer, 0, writeBuffer, 0, _currentBufferPosition ); + } + + DBObject chunk = createChunk(_id, _currentChunkNumber, writeBuffer); + + _fs._chunkCollection.save( chunk ); + + _currentChunkNumber++; + _totalBytes += writeBuffer.length; + _messageDigester.update( writeBuffer ); + _currentBufferPosition = 0; + } + + protected DBObject createChunk(Object id, int currentChunkNumber, byte[] writeBuffer) { + return BasicDBObjectBuilder.start() + .add("files_id", id) + .add("n", currentChunkNumber) + .add("data", writeBuffer).get(); + } + + /** + * Reads a buffer full from the {@link java.io.InputStream}. + * + * @return Number of bytes read from stream. + * @throws IOException + * if the reading from the stream fails. + */ + private int _readStream2Buffer() throws IOException { + int bytesRead = 0; + while ( _currentBufferPosition < _chunkSize && bytesRead >= 0 ) { + bytesRead = _in.read( _buffer, _currentBufferPosition, + (int) _chunkSize - _currentBufferPosition ); + if ( bytesRead > 0 ) { + _currentBufferPosition += bytesRead; + } else if ( bytesRead == 0 ) { + throw new RuntimeException( "i'm doing something wrong" ); + } + } + return bytesRead; + } + + /** + * Marks the data as fully written. This needs to be called before super.save() + */ + private void _finishData() { + if (!_savedChunks) { + _md5 = Util.toHex( _messageDigester.digest() ); + _md5Pool.done( _messageDigester ); + _messageDigester = null; + _length = _totalBytes; + _savedChunks = true; + try { + if ( _in != null && _closeStreamOnPersist ) + _in.close(); + } catch (IOException e) { + //ignore + } + } + } + + private final InputStream _in; + private boolean _closeStreamOnPersist; + private boolean _savedChunks = false; + private byte[] _buffer = null; + private int _currentChunkNumber = 0; + private int _currentBufferPosition = 0; + private long _totalBytes = 0; + private MessageDigest _messageDigester = null; + private OutputStream _outputStream = null; + + /** + * A pool of {@link java.security.MessageDigest} objects. + */ + static SimplePool _md5Pool + = new SimplePool( "md5" , 10 , -1 , false , false ) { + /** + * {@inheritDoc} + * + * @see com.massivecraft.mcore3.lib.mongodb.util.SimplePool#createNew() + */ + protected MessageDigest createNew() { + try { + return MessageDigest.getInstance( "MD5" ); + } catch ( java.security.NoSuchAlgorithmException e ) { + throw new RuntimeException( "your system doesn't have md5!" ); + } + } + }; + + /** + * An output stream implementation that can be used to successively write to + * a GridFS file. + * + * @author Guy K. Kloss + */ + class MyOutputStream extends OutputStream { + + /** + * {@inheritDoc} + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write( int b ) throws IOException { + byte[] byteArray = new byte[1]; + byteArray[0] = (byte) (b & 0xff); + write( byteArray, 0, 1 ); + } + + /** + * {@inheritDoc} + * + * @see java.io.OutputStream#write(byte[], int, int) + */ + @Override + public void write( byte[] b , int off , int len ) throws IOException { + int offset = off; + int length = len; + int toCopy = 0; + while ( length > 0 ) { + toCopy = length; + if ( toCopy > _chunkSize - _currentBufferPosition ) { + toCopy = (int) _chunkSize - _currentBufferPosition; + } + System.arraycopy( b, offset, _buffer, _currentBufferPosition, toCopy ); + _currentBufferPosition += toCopy; + offset += toCopy; + length -= toCopy; + if ( _currentBufferPosition == _chunkSize ) { + _dumpBuffer( false ); + } + } + } + + /** + * Processes/saves all data from {@link java.io.InputStream} and closes + * the potentially present {@link java.io.OutputStream}. The GridFS file + * will be persisted afterwards. + */ + @Override + public void close() { + // write last buffer if needed + _dumpBuffer( true ); + // finish stream + _finishData(); + // save file obj + GridFSInputFile.super.save(); + } + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/gridfs/package.html b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/package.html new file mode 100644 index 00000000..5693da42 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/gridfs/package.html @@ -0,0 +1,3 @@ + + GridFS tools. Used for storing files in MongoDB + diff --git a/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferFactory.java new file mode 100644 index 00000000..8f835609 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferFactory.java @@ -0,0 +1,37 @@ +// ByteBufferFactory.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.io; + +import java.nio.ByteBuffer; + +public interface ByteBufferFactory { + public ByteBuffer get(); + + public static class SimpleHeapByteBufferFactory implements ByteBufferFactory { + public SimpleHeapByteBufferFactory( int size ){ + _size = size; + } + + public ByteBuffer get(){ + return ByteBuffer.wrap( new byte[_size] ); + } + + final int _size; + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferHolder.java b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferHolder.java new file mode 100644 index 00000000..c90b2109 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferHolder.java @@ -0,0 +1,127 @@ +// ByteBufferHolder.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.io; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class ByteBufferHolder { + + public ByteBufferHolder(){ + this( 1024 * 1024 * 1024 ); // 1gb + } + + public ByteBufferHolder( int max ){ + _max = max; + } + + public byte get( int i ){ + if ( i >= _pos ) + throw new RuntimeException( "out of bounds" ); + + final int num = i / _bufSize; + final int pos = i % _bufSize; + + return _buffers.get( num ).get( pos ); + } + + public void get( int pos , byte b[] ){ + for ( int i=0; i= _pos ) + throw new RuntimeException( "out of bounds" ); + + final int num = i / _bufSize; + final int pos = i % _bufSize; + + _buffers.get( num ).put( pos , val ); + } + + public int position(){ + return _pos; + } + + public void position( int p ){ + _pos = p; + int num = _pos / _bufSize; + int pos = _pos % _bufSize; + + while ( _buffers.size() <= num ) + _addBucket(); + + ByteBuffer bb = _buffers.get( num ); + bb.position( pos ); + for ( int i=num+1; i<_buffers.size(); i++ ) + _buffers.get( i ).position( 0 ); + } + + public int remaining(){ + return Integer.MAX_VALUE; + } + + public void put( ByteBuffer in ){ + while ( in.hasRemaining() ){ + int num = _pos / _bufSize; + if ( num >= _buffers.size() ) + _addBucket(); + + ByteBuffer bb = _buffers.get( num ); + + final int canRead = Math.min( bb.remaining() , in.remaining() ); + + final int oldLimit = in.limit(); + in.limit( in.position() + canRead ); + + bb.put( in ); + + in.limit( oldLimit ); + + _pos += canRead; + } + + } + + private void _addBucket(){ + if ( capacity() + _bufSize > _max ) + throw new RuntimeException( "too big current:" + capacity() ); + _buffers.add( ByteBuffer.allocateDirect( _bufSize ) ); + } + + public int capacity(){ + return _buffers.size() * _bufSize; + } + + public String toString(){ + StringBuilder buf = new StringBuilder(); + buf.append( "{ ByteBufferHolder pos:" + _pos + " " ); + for ( ByteBuffer bb : _buffers ) + buf.append( bb ).append( " " ); + return buf.append( "}" ).toString(); + } + + List _buffers = new ArrayList(); + int _pos = 0; + final int _max; + + static final int _bufSize = 4096; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferInputStream.java b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferInputStream.java new file mode 100644 index 00000000..e25b78dc --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferInputStream.java @@ -0,0 +1,120 @@ +// ByteBufferInputStream.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.io; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.List; + +public class ByteBufferInputStream extends InputStream { + + public ByteBufferInputStream( List lst ){ + this( lst , false ); + } + + public ByteBufferInputStream( List lst , boolean flip ){ + _lst = lst; + if ( flip ) + for ( ByteBuffer buf : _lst ) + buf.flip(); + } + + public int available(){ + int avail = 0; + for ( int i=_pos; i<_lst.size(); i++ ) + avail += _lst.get( i ).remaining(); + return avail; + } + + public void close(){} + + public void mark(int readlimit){ + throw new RuntimeException( "mark not supported" ); + } + + public void reset(){ + throw new RuntimeException( "mark not supported" ); + } + + public boolean markSupported(){ + return false; + } + + public int read(){ + if ( _pos >= _lst.size() ) + return -1; + + ByteBuffer buf = _lst.get( _pos ); + if ( buf.remaining() > 0 ) + return buf.get() & 0xff; + + _pos++; + return read(); + } + + public int read(byte[] b){ + return read( b , 0 , b.length ); + } + + public int read(byte[] b, int off, int len){ + if ( _pos >= _lst.size() ) + return -1; + + ByteBuffer buf = _lst.get( _pos ); + + if ( buf.remaining() == 0 ){ + _pos++; + return read( b , off , len ); + } + + int toRead = Math.min( len , buf.remaining() ); + buf.get( b , off , toRead ); + + if ( toRead == len || _pos + 1 >= _lst.size() ) + return toRead; + + _pos++; + return toRead + read( b , off + toRead , len - toRead ); + } + + + public long skip(long n){ + long skipped = 0; + + while ( n >= 0 && _pos < _lst.size() ){ + ByteBuffer b = _lst.get( _pos ); + if ( b.remaining() < n ){ + skipped += b.remaining(); + n -= b.remaining(); + b.position( b.limit() ); + _pos++; + continue; + } + + skipped += n; + b.position( (int)(b.position() + n) ); + return skipped; + } + + return skipped; + } + + final List _lst; + private int _pos = 0; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferOutputStream.java b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferOutputStream.java new file mode 100644 index 00000000..57bf4be4 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferOutputStream.java @@ -0,0 +1,95 @@ +// ByteBufferOutputStream.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.io; + +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class ByteBufferOutputStream extends OutputStream { + + public ByteBufferOutputStream(){ + this( _defaultFactory ); + } + + public ByteBufferOutputStream( int size ){ + this( new ByteBufferFactory.SimpleHeapByteBufferFactory( size ) ); + } + + public ByteBufferOutputStream( ByteBufferFactory factory ){ + _factory = factory; + } + + public void close(){ + } + + public void flush(){ + } + + public void write(byte[] b){ + write( b , 0 , b.length ); + } + + public void write(byte[] b, int off, int len){ + ByteBuffer cur = _need( 1 ); + + int toWrite = Math.min( len , cur.remaining() ); + cur.put( b , off , toWrite ); + + if ( toWrite == len ) + return; + + write( b , off + toWrite , len - toWrite ); + } + + public void write(int b){ + _need(1).put((byte)b); + } + + public List getBuffers(){ + return _lst; + } + + public List getBuffers( boolean flip ){ + if ( flip ) + for ( ByteBuffer buf : _lst ) + buf.flip(); + return _lst; + } + + private ByteBuffer _need( int space ){ + if ( _lst.size() == 0 ){ + _lst.add( _factory.get() ); + return _lst.get( 0 ); + } + + ByteBuffer cur = _lst.get( _lst.size() - 1 ); + if ( space <= cur.remaining() ) + return cur; + + _lst.add( _factory.get() ); + return _lst.get( _lst.size() - 1 ); + } + + final List _lst = new ArrayList(); + final ByteBufferFactory _factory; + + static final ByteBufferFactory _defaultFactory = new ByteBufferFactory.SimpleHeapByteBufferFactory( 1024 * 4 ); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/io/ByteStream.java b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteStream.java new file mode 100644 index 00000000..04d41f42 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/io/ByteStream.java @@ -0,0 +1,28 @@ +// ByteStream.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.io; + +import java.nio.ByteBuffer; + +public interface ByteStream { + + public boolean hasMore(); + public int write( ByteBuffer bb ); + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/package.html b/src/com/massivecraft/mcore3/lib/mongodb/package.html new file mode 100644 index 00000000..084ef185 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/package.html @@ -0,0 +1,3 @@ + +

    Main package with core files. @see Mongo is the main entry point.

    + diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/AbstractObjectSerializer.java b/src/com/massivecraft/mcore3/lib/mongodb/util/AbstractObjectSerializer.java new file mode 100644 index 00000000..3d5023c0 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/AbstractObjectSerializer.java @@ -0,0 +1,27 @@ +/** +* Copyright (c) 2008 - 2011 10gen, Inc. +*

    +* 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.massivecraft.mcore3.lib.mongodb.util; + +abstract class AbstractObjectSerializer implements ObjectSerializer { + + @Override + public String serialize(final Object obj) { + StringBuilder builder = new StringBuilder(); + serialize(obj, builder); + return builder.toString(); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/Args.java b/src/com/massivecraft/mcore3/lib/mongodb/util/Args.java new file mode 100644 index 00000000..228575a4 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/Args.java @@ -0,0 +1,81 @@ +// Args.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Args { + public Args( String args[] ){ + + for ( String s : args ){ + + if ( s.startsWith( "-" ) ){ + s = s.substring(1); + int idx = s.indexOf( "=" ); + if ( idx < 0 ) + _options.put( s , "" ); + else + _options.put( s.substring( 0 , idx ) , s.substring( idx + 1 ) ); + continue; + } + + _params.add( s ); + + } + } + + public String getOption( String name ){ + return _options.get( name ); + } + + public String toString(){ + StringBuilder s = new StringBuilder(); + + for ( String p : _options.keySet() ){ + s.append( '-' ).append( p ); + + String v = _options.get( p ); + if ( v.length() == 0 ) + continue; + + s.append( '=' ); + + if ( v.indexOf( " " ) >= 0 ) + s.append( '"' ).append( v ).append( '"' ); + else + s.append( v ); + } + + for ( String p : _params ){ + s.append( ' ' ); + if ( p.indexOf( " " ) >= 0 ) + s.append( '"' ).append( p ).append( '"' ); + else + s.append( p ); + } + + return s.toString(); + } + + final Map _options = new HashMap(); + final List _params = new ArrayList(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/Base64Codec.java b/src/com/massivecraft/mcore3/lib/mongodb/util/Base64Codec.java new file mode 100644 index 00000000..82ad4f16 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/Base64Codec.java @@ -0,0 +1,117 @@ + +/** + * Copyright (C) 2012 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +/* + * Copyright (C) 2012 10gen Inc. + * + * 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. + */ + +/** + * Provides Base64 encoding and decoding . + * + *

    + * This class implements Base64 encoding + * + * Thanks to Apache Commons project. This class refactored from org.apache.commons.codec.binary + * + * Original Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

    + * + */ +public class Base64Codec { + + private static int BYTES_PER_UNENCODED_BLOCK = 3; + private static int BYTES_PER_ENCODED_BLOCK = 4; + + /** Mask used to extract 6 bits, used when encoding */ + private static final int SixBitMask = 0x3f; + + /** padding char */ + private static final byte PAD = '='; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + * + */ + private static final byte[] EncodeTable = { 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', '/' }; + + + public String encode(byte[] in) { + + int modulus = 0; + int bitWorkArea = 0; + int numEncodedBytes = (in.length/BYTES_PER_UNENCODED_BLOCK)*BYTES_PER_ENCODED_BLOCK + + ((in.length%BYTES_PER_UNENCODED_BLOCK == 0 )?0:4); + + byte[] buffer = new byte[numEncodedBytes]; + int pos = 0; + + for (int i = 0; i < in.length; i++) { + modulus = (modulus+1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[i]; + + if (b < 0) + b += 256; + + bitWorkArea = (bitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract + buffer[pos++] = EncodeTable[(bitWorkArea >> 18) & SixBitMask]; + buffer[pos++] = EncodeTable[(bitWorkArea >> 12) & SixBitMask]; + buffer[pos++] = EncodeTable[(bitWorkArea >> 6) & SixBitMask]; + buffer[pos++] = EncodeTable[bitWorkArea & SixBitMask]; + } + } + + switch (modulus) { // 0-2 + case 1 : // 8 bits = 6 + 2 + buffer[pos++] = EncodeTable[(bitWorkArea >> 2) & SixBitMask]; // top 6 bits + buffer[pos++] = EncodeTable[(bitWorkArea << 4) & SixBitMask]; // remaining 2 + buffer[pos++] = PAD; + buffer[pos++] = PAD; + break; + + case 2 : // 16 bits = 6 + 6 + 4 + buffer[pos++] = EncodeTable[(bitWorkArea >> 10) & SixBitMask]; + buffer[pos++] = EncodeTable[(bitWorkArea >> 4) & SixBitMask]; + buffer[pos++] = EncodeTable[(bitWorkArea << 2) & SixBitMask]; + buffer[pos++] = PAD; + break; + } + + return new String(buffer); + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/ClassMapBasedObjectSerializer.java b/src/com/massivecraft/mcore3/lib/mongodb/util/ClassMapBasedObjectSerializer.java new file mode 100644 index 00000000..57c24523 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/ClassMapBasedObjectSerializer.java @@ -0,0 +1,85 @@ + +/** + * Copyright (C) 2012 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import com.massivecraft.mcore3.lib.bson.util.ClassMap; +import com.massivecraft.mcore3.lib.mongodb.Bytes; + + +import java.util.List; + +/** + * Objects of type ClassMapBasedObjectSerializer are constructed to perform + * instance specific object to JSON serialization schemes. + *

    + * This class is not thread safe + * + * @author breinero + */ +class ClassMapBasedObjectSerializer extends AbstractObjectSerializer { + + /** + * Assign a ObjectSerializer to perform a type specific serialization scheme + * @param c this object's type serves as a key in the serialization map. + * ClassMapBasedObjectSerializer uses org.bson.util.ClassMap and not only checks if 'c' is a key in the Map, + * but also walks the up superclass and interface graph of 'c' to find matches. + * This means that it is only necessary assign ObjectSerializers to base classes. @see org.bson.util.ClassMap + * @param serializer performs the serialization mapping specific to the @param key type + */ + @SuppressWarnings("rawtypes") + void addObjectSerializer(Class c, ObjectSerializer serializer) { + _serializers.put(c , serializer); + } + + /** + * + * @param obj the object to be serialized + * @param buf StringBuilder containing the JSON representation of the object + */ + @Override + public void serialize(Object obj, StringBuilder buf){ + + obj = Bytes.applyEncodingHooks( obj ); + + if(obj == null) { + buf.append(" null "); + return; + } + + ObjectSerializer serializer = null; + + List> ancestors; + ancestors = ClassMap.getAncestry(obj.getClass()); + + for (final Class ancestor : ancestors) { + serializer = _serializers.get(ancestor); + if (serializer != null) + break; + } + + if (serializer == null && obj.getClass().isArray()) + serializer = _serializers.get(Object[].class); + + if (serializer == null) + throw new RuntimeException( "json can't serialize type : " + obj.getClass() ); + + serializer.serialize(obj, buf); + } + + private ClassMap _serializers = new ClassMap(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/FastStack.java b/src/com/massivecraft/mcore3/lib/mongodb/util/FastStack.java new file mode 100644 index 00000000..a9cfdb86 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/FastStack.java @@ -0,0 +1,55 @@ +// FastStack.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.ArrayList; +import java.util.List; + +public class FastStack{ + + public void push( T t ){ + _data.add( t ); + } + + public T peek(){ + return _data.get( _data.size() - 1 ); + } + + public T pop(){ + return _data.remove( _data.size() - 1 ); + } + + public int size(){ + return _data.size(); + } + + public void clear(){ + _data.clear(); + } + + public T get( int i ){ + return _data.get( i ); + } + + public String toString(){ + return _data.toString(); + } + + private final List _data = new ArrayList(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/Hash.java b/src/com/massivecraft/mcore3/lib/mongodb/util/Hash.java new file mode 100644 index 00000000..3ae16e17 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/Hash.java @@ -0,0 +1,249 @@ +// Hash.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +public final class Hash { + + /** Creates a hash for a string. + * @param s String to hash + * @return the hash code + */ + public static final int hashBackward( String s ) { + int hash = 0; + for ( int i = s.length()-1; i >= 0; i-- ) + hash = hash * 31 + s.charAt( i ); + return hash; + } + + /** Creates a long hash for a string. + * @param s the string to hash + * @return the hash code + */ + public static final long hashBackwardLong( String s ) { + long hash = 0; + for ( int i = s.length()-1; i >= 0; i-- ) + hash = hash * 63 + s.charAt( i ); + return hash; + } + + /** @unexpose */ + static final long _longHashConstant = 4095; + + /** + * 64-bit hash, using longs, in stead of ints, for less collisions, for when it matters. + * Calls longHash( s , 0 , s.length() ) + * @param s The String to hash. + * @return the hash code + */ + public static final long longHash( String s ) { + return longHash( s , 0 , s.length() ); + } + + /** + * 64-bit hash using longs, starting on index 'start' and including everything before 'end'. + * @param s The string to hash. + * @param start Where to start the hash. + * @param end Where to end the hash. + * @return the hash code + */ + public static final long longHash( String s , int start , int end ) { + long hash = 0; + for ( ; start < end; start++ ) + hash = _longHashConstant * hash + s.charAt( start ); + return hash; + } + + /** + * Same as longHash(String), using only lower-case values of letters. + * Calls longhash( s , 0 , s.length() ). + * @param s The string to Hash. + * @return the hash code + */ + public static final long longLowerHash( String s ) { + return longLowerHash( s , 0 , s.length() ); + } + + /** + * Long (64-bit) hash, lower-cased, from [start-end) + * @param s The string to hash. + * @param start where to start hashing. + * @param end Where to stop hashing. + * @return the hash code + */ + public static final long longLowerHash( String s , int start , int end ) { + long hash = 0; + for ( ; start < end; start++ ) + hash = _longHashConstant * hash + Character.toLowerCase( s.charAt( start ) ); + return hash; + } + + /** + * Long (64-bit) hash, lower-cased, from [start-end) + * @param s The string to hash. + * @param start where to start hashing. + * @param end Where to stop hashing. + * @return the hash code + */ + public static final long longLowerHash( String s , int start , int end , long hash ) { + for ( ; start < end; start++ ) + hash = _longHashConstant * hash + Character.toLowerCase( s.charAt( start ) ); + return hash; + } + + /** Adds the lower-case equivalent of a character to an existing hash code. + * @param hash the existing hash code + * @param c the character to add + * @return the hash code + */ + public static final long longLowerHashAppend( long hash , char c ) { + return hash * _longHashConstant + Character.toLowerCase( c ); + } + + /** Adds a character to an existing hash code. + * @param hash the existing hash code + * @param c the character to add + * @return the hash code + */ + public static final long longHashAppend( long hash , char c ) { + return hash * _longHashConstant + c; + } + + /** + * This is an exact copy of the String hashCode() function, aside from the lowercasing. + * @param s string to be hashed + * @return the hash code + */ + public static final int lowerCaseHash( String s ) { + int h = 0; + final int len = s.length(); + for ( int i = 0; i < len; i++ ) + h = 31*h + Character.toLowerCase( s.charAt( i ) ); + return h; + } + + /** + * Creates a hash code of a lowercase string from [start-end) + * @param s string to be hashed + * @param start the starting index + * @param end the ending index + * @return the hash code + */ + public static final int lowerCaseHash( String s , int start , int end ) { + int h = 0; + final int len = s.length(); + for ( int i = start; i < len && i < end; i++ ) + h = 31*h + Character.toLowerCase( s.charAt( i ) ); + return h; + } + + /** + * Creates a hash code of a string from [start-end) + * @param s string to be hashed + * @param start the starting index + * @param end the ending index + * @return the hash code + */ + public static final int hashCode( CharSequence s , int start , int end ) { + int h = 0; + final int len = s.length(); + for ( int i = start; i < len && i < end; i++ ) + h = 31*h + s.charAt( i ); + return h; + } + + /** + * Creates a hash code of a lowercase string with whitespace removed from [start-end) + * @param s string to be hashed + * @param start the starting index + * @param end the ending index + * @return the hash code + */ + public static final int nospaceLowerHash( String s , int start , int end ) { + int h = 0; + final int len = s.length(); + for ( int i = start; i < len && i < end; i++ ) { + char c = s.charAt( i ); + if ( Character.isWhitespace( c ) ) + continue; + h = 31*h + Character.toLowerCase( c ); + } + return h; + } + + /** + * This is an exact copy of the String hashCode() function, aside from the lowercasing. + * No, it's not. It also ignores consecutive whitespace. + * @param s string to be hashed + * @return the hash code + */ + public static final int lowerCaseSpaceTrimHash( String s ) { + int h = 0; + int len = s.length(); + while ( len > 1 && Character.isWhitespace( s.charAt( len-1 ) ) ) + len--; + boolean lastWasSpace = true; + for ( int i = 0; i < len; i++ ) { + boolean isSpace = Character.isWhitespace( s.charAt( i ) ); + if ( isSpace && lastWasSpace ) + continue; + lastWasSpace = isSpace; + h = 31*h + Character.toLowerCase( s.charAt( i ) ); + } + return h; + } + + /** + * Creates a hash code of a lowercase string from [start-end) ignoring whitespace + * @param s string to be hashed + * @param start the starting index + * @param end the ending index + * @return the hash code + */ + public static final int lowerCaseSpaceTrimHash( String s , int start , int end ) { + int h = 0; + int len = s.length(); + while ( len > 1 && Character.isWhitespace( s.charAt( len-1 ) ) ) + len--; + boolean lastWasSpace = true; + for ( int i = start; i < len && i < end; i++ ) { + boolean isSpace = Character.isWhitespace( s.charAt( i ) ); + if ( isSpace && lastWasSpace ) + continue; + lastWasSpace = isSpace; + h = 31*h + Character.toLowerCase( s.charAt( i ) ); + } + return h; + } + + /** + * Calculate the hashcode for a series of strings combined as one. + * @param strings Varargs array of Strings. + * @return A hashcode. + */ + public static final int hashCode( String ... strings ) { + int h = 0; + for ( String s : strings ) { + int len = s.length(); + for ( int i = 0; i < len; i++ ) + h = 31*h + s.charAt( i ); + } + return h; + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/IdentitySet.java b/src/com/massivecraft/mcore3/lib/mongodb/util/IdentitySet.java new file mode 100644 index 00000000..4f2a9a74 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/IdentitySet.java @@ -0,0 +1,81 @@ +// IdentitySet.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Iterator; + +public class IdentitySet implements Iterable { + + public IdentitySet(){ + } + + public IdentitySet( Iterable copy ){ + for ( T t : copy ) + add( t ); + } + + public boolean add( T t ){ + return _map.put( t , "a" ) == null; + } + + public boolean contains( T t ){ + return _map.containsKey( t ); + } + + public void remove( T t ){ + _map.remove( t ); + } + + public void removeall( Iterable coll ){ + for ( T t : coll ) + _map.remove( t ); + } + + public void clear(){ + _map.clear(); + } + + public int size(){ + return _map.size(); + } + + public Iterator iterator(){ + return _map.keySet().iterator(); + } + + public void addAll( Collection c ){ + for ( T t : c ){ + add( t ); + } + } + + public void addAll( IdentitySet c ){ + for ( T t : c ) + add( t ); + } + + public void removeAll( Iterable prev ){ + for ( T t : prev ) + remove( t ); + } + + final IdentityHashMap _map = new IdentityHashMap(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/JSON.java b/src/com/massivecraft/mcore3/lib/mongodb/util/JSON.java new file mode 100644 index 00000000..90c1cd6b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/JSON.java @@ -0,0 +1,560 @@ +// JSON.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + + +import com.massivecraft.mcore3.lib.bson.BSONCallback; +import com.massivecraft.mcore3.lib.mongodb.DBObject; + +/** + * Helper methods for JSON serialization and de-serialization + */ +public class JSON { + + /** + * Serializes an object into it's JSON form. + *

    + * This method delegates serialization to JSONSerializers.getLegacy + * + * @param o object to serialize + * @return String containing JSON form of the object + * @see com.massivecraft.mcore3.lib.mongodb.util.JSONSerializers#getLegacy() + */ + public static String serialize( Object o ){ + StringBuilder buf = new StringBuilder(); + serialize( o , buf ); + return buf.toString(); + } + + /** + * Serializes an object into it's JSON form + *

    + * This method delegates serialization to JSONSerializers.getLegacy + * + * @param o object to serialize + * @param buf StringBuilder containing the JSON representation under construction + * @return String containing JSON form of the object + * @see com.massivecraft.mcore3.lib.mongodb.util.JSONSerializers#getLegacy() + */ + public static void serialize( Object o, StringBuilder buf) { + JSONSerializers.getLegacy().serialize(o, buf); + } + + /** + * Parses a JSON string representing a JSON value + * + * @param s the string to parse + * @return the object + */ + public static Object parse( String s ){ + return parse(s, null); + } + + /** + * Parses a JSON string representing a JSON value + * + * @param s the string to parse + * @return the object + */ + public static Object parse( String s, BSONCallback c ){ + if (s == null || (s=s.trim()).equals("")) { + return (DBObject)null; + } + + JSONParser p = new JSONParser(s, c); + return p.parse(); + } + + static void string( StringBuilder a , String s ){ + a.append("\""); + for(int i = 0; i < s.length(); ++i){ + char c = s.charAt(i); + if (c == '\\') + a.append("\\\\"); + else if(c == '"') + a.append("\\\""); + else if(c == '\n') + a.append("\\n"); + else if(c == '\r') + a.append("\\r"); + else if(c == '\t') + a.append("\\t"); + else if(c == '\b') + a.append("\\b"); + else if ( c < 32 ) + continue; + else + a.append(c); + } + a.append("\""); + } +} + + +/** + * Parser for JSON objects. + * + * Supports all types described at www.json.org, except for + * numbers with "e" or "E" in them. + */ +class JSONParser { + + String s; + int pos = 0; + BSONCallback _callback; + + /** + * Create a new parser. + */ + public JSONParser(String s) { + this(s, null); + } + + /** + * Create a new parser. + */ + public JSONParser(String s, BSONCallback callback) { + this.s = s; + _callback = (callback == null) ? new JSONCallback() : callback; + } + + + /** + * Parse an unknown type. + * + * @return Object the next item + * @throws JSONParseException if invalid JSON is found + */ + public Object parse() { + return parse(null); + } + + /** + * Parse an unknown type. + * + * @return Object the next item + * @throws JSONParseException if invalid JSON is found + */ + protected Object parse(String name) { + Object value = null; + char current = get(); + + switch(current) { + // null + case 'n': + read('n'); read('u'); read('l'); read('l'); + value = null; + break; + // NaN + case 'N': + read('N'); read('a'); read('N'); + value = Double.NaN; + break; + // true + case 't': + read('t'); read('r'); read('u'); read('e'); + value = true; + break; + // false + case 'f': + read('f'); read('a'); read('l'); read('s'); read('e'); + value = false; + break; + // string + case '\'': + case '\"': + value = parseString(true); + break; + // number + case '0': case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '+': case '-': + value = parseNumber(); + break; + // array + case '[': + value = parseArray(name); + break; + // object + case '{': + value = parseObject(name); + break; + default: + throw new JSONParseException(s, pos); + } + return value; + } + + /** + * Parses an object for the form {} and { members }. + * + * @return DBObject the next object + * @throws JSONParseException if invalid JSON is found + */ + public Object parseObject() { + return parseObject(null); + } + + /** + * Parses an object for the form {} and { members }. + * + * @return DBObject the next object + * @throws JSONParseException if invalid JSON is found + */ + @SuppressWarnings("unused") + protected Object parseObject(String name){ + if (name != null) { + _callback.objectStart(name); + } else { + _callback.objectStart(); + } + + read('{'); + char current = get(); + while(get() != '}') { + String key = parseString(false); + read(':'); + Object value = parse(key); + doCallback(key, value); + + if((current = get()) == ',') { + read(','); + } + else { + break; + } + } + read('}'); + + return _callback.objectDone(); + } + + protected void doCallback(String name, Object value) { + if (value == null) { + _callback.gotNull(name); + } else if (value instanceof String) { + _callback.gotString(name, (String)value); + } else if (value instanceof Boolean) { + _callback.gotBoolean(name, (Boolean)value); + } else if (value instanceof Integer) { + _callback.gotInt(name, (Integer)value); + } else if (value instanceof Long) { + _callback.gotLong(name, (Long)value); + } else if (value instanceof Double) { + _callback.gotDouble(name, (Double)value); + } + } + + /** + * Read the current character, making sure that it is the expected character. + * Advances the pointer to the next character. + * + * @param ch the character expected + * + * @throws JSONParseException if the current character does not match the given character + */ + public void read(char ch) { + if(!check(ch)) { + throw new JSONParseException(s, pos); + } + pos++; + } + + public char read(){ + if ( pos >= s.length() ) + throw new IllegalStateException( "string done" ); + return s.charAt( pos++ ); + } + + /** + * Read the current character, making sure that it is a hexidecimal character. + * + * @throws JSONParseException if the current character is not a hexidecimal character + */ + public void readHex() { + if (pos < s.length() && + ((s.charAt(pos) >= '0' && s.charAt(pos) <= '9') || + (s.charAt(pos) >= 'A' && s.charAt(pos) <= 'F') || + (s.charAt(pos) >= 'a' && s.charAt(pos) <= 'f'))) { + pos++; + } + else { + throw new JSONParseException(s, pos); + } + } + + /** + * Checks the current character, making sure that it is the expected character. + * + * @param ch the character expected + * + * @throws JSONParseException if the current character does not match the given character + */ + public boolean check(char ch) { + return get() == ch; + } + + /** + * Advances the position in the string past any whitespace. + */ + public void skipWS() { + while(pos < s.length() && Character.isWhitespace(s.charAt(pos))) { + pos++; + } + } + + /** + * Returns the current character. + * Returns -1 if there are no more characters. + * + * @return the next character + */ + public char get() { + skipWS(); + if(pos < s.length()) + return s.charAt(pos); + return (char)-1; + } + + /** + * Parses a string. + * + * @return the next string. + * @throws JSONParseException if invalid JSON is found + */ + public String parseString(boolean needQuote) { + char quot = 0; + if(check('\'')) + quot = '\''; + else if(check('\"')) + quot = '\"'; + else if (needQuote) + throw new JSONParseException(s, pos); + + char current; + + if (quot > 0) + read(quot); + StringBuilder buf = new StringBuilder(); + int start = pos; + while(pos < s.length()) { + current = s.charAt(pos); + if (quot > 0) { + if (current == quot) + break; + } else { + if (current == ':' || current == ' ') + break; + } + + if(current == '\\') { + pos++; + + char x = get(); + + char special = 0; + + switch ( x ){ + + case 'u': + { // decode unicode + buf.append(s.substring(start, pos-1)); + pos++; + int tempPos = pos; + + readHex(); + readHex(); + readHex(); + readHex(); + + int codePoint = Integer.parseInt(s.substring(tempPos, tempPos+4), 16); + buf.append((char)codePoint); + + start = pos; + continue; + } + case 'n': special = '\n'; break; + case 'r': special = '\r'; break; + case 't': special = '\t'; break; + case 'b': special = '\b'; break; + case '"': special = '\"'; break; + case '\\': special = '\\'; break; + } + + buf.append(s.substring(start, pos-1)); + if ( special != 0 ){ + pos++; + buf.append( special ); + } + start = pos; + continue; + } + pos++; + } + buf.append(s.substring(start, pos)); + if (quot > 0) + read(quot); + return buf.toString(); + } + + /** + * Parses a number. + * + * @return the next number (int or double). + * @throws JSONParseException if invalid JSON is found + */ + @SuppressWarnings("unused") + public Number parseNumber() { + + char current = get(); + int start = this.pos; + boolean isDouble = false; + + if(check('-') || check('+')) { + pos++; + } + + outer: + while(pos < s.length()) { + switch(s.charAt(pos)) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + pos++; + break; + case '.': + isDouble = true; + parseFraction(); + break; + case 'e': case 'E': + isDouble = true; + parseExponent(); + break; + default: + break outer; + } + } + + try{ + if (isDouble) + return Double.valueOf(s.substring(start, pos)); + + Long val = Long.valueOf(s.substring(start, pos)); + if (val <= Integer.MAX_VALUE && val >= Integer.MIN_VALUE) + return val.intValue(); + return val; + }catch(NumberFormatException e){ + throw new JSONParseException(s, start, e); + } + } + + /** + * Advances the pointed through .digits. + */ + public void parseFraction() { + // get past . + pos++; + + outer: + while(pos < s.length()) { + switch(s.charAt(pos)) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + pos++; + break; + case 'e': case 'E': + parseExponent(); + break; + default: + break outer; + } + } + } + + /** + * Advances the pointer through the exponent. + */ + public void parseExponent() { + // get past E + pos++; + + if(check('-') || check('+')) { + pos++; + } + + outer: + while(pos < s.length()) { + switch(s.charAt(pos)) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + pos++; + break; + default: + break outer; + } + } + } + + /** + * Parses the next array. + * + * @return the array + * @throws JSONParseException if invalid JSON is found + */ + public Object parseArray() { + return parseArray(null); + } + + /** + * Parses the next array. + * + * @return the array + * @throws JSONParseException if invalid JSON is found + */ + protected Object parseArray(String name) { + if (name != null) { + _callback.arrayStart(name); + } else { + _callback.arrayStart(); + } + + read('['); + + int i = 0; + char current = get(); + while( current != ']' ) { + String elemName = String.valueOf(i++); + Object elem = parse(elemName); + doCallback(elemName, elem); + + if((current = get()) == ',') { + read(','); + } + else if(current == ']') { + break; + } + else { + throw new JSONParseException(s, pos); + } + } + + read(']'); + + return _callback.arrayDone(); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/JSONCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/util/JSONCallback.java new file mode 100644 index 00000000..62d3ff37 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/JSONCallback.java @@ -0,0 +1,161 @@ +// JSONCallback.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.SimpleTimeZone; +import java.util.UUID; +import java.util.regex.Pattern; + + +import com.massivecraft.mcore3.lib.bson.BSON; +import com.massivecraft.mcore3.lib.bson.BSONObject; +import com.massivecraft.mcore3.lib.bson.BasicBSONCallback; +import com.massivecraft.mcore3.lib.bson.types.BSONTimestamp; +import com.massivecraft.mcore3.lib.bson.types.Code; +import com.massivecraft.mcore3.lib.bson.types.CodeWScope; +import com.massivecraft.mcore3.lib.bson.types.MaxKey; +import com.massivecraft.mcore3.lib.bson.types.MinKey; +import com.massivecraft.mcore3.lib.bson.types.ObjectId; +import com.massivecraft.mcore3.lib.mongodb.BasicDBList; +import com.massivecraft.mcore3.lib.mongodb.BasicDBObject; +import com.massivecraft.mcore3.lib.mongodb.DBObject; +import com.massivecraft.mcore3.lib.mongodb.DBRef; + +public class JSONCallback extends BasicBSONCallback { + + @Override + public BSONObject create(){ + return new BasicDBObject(); + } + + @Override + protected BSONObject createList() { + return new BasicDBList(); + } + + public void objectStart(boolean array, String name){ + _lastArray = array; + super.objectStart( array , name ); + } + + public Object objectDone(){ + String name = curName(); + Object o = super.objectDone(); + BSONObject b = (BSONObject)o; + + // override the object if it's a special type + if (!_lastArray) { + if (b.containsField("$oid")) { + o = new ObjectId((String) b.get("$oid")); + if (!isStackEmpty()) { + gotObjectId(name, (ObjectId) o); + } else { + setRoot(o); + } + } else if (b.containsField("$date")) { + + if(b.get("$date") instanceof Number){ + o = new Date(((Number)b.get("$date")).longValue()); + }else { + SimpleDateFormat format = new SimpleDateFormat(_msDateFormat); + format.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT"))); + o = format.parse(b.get("$date").toString(), new ParsePosition(0)); + + if (o == null) { + // try older format with no ms + format = new SimpleDateFormat(_secDateFormat); + format.setCalendar(new GregorianCalendar(new SimpleTimeZone(0, "GMT"))); + o = format.parse(b.get("$date").toString(), new ParsePosition(0)); + } + } + if (!isStackEmpty()) { + cur().put(name, o); + } else { + setRoot(o); + } + } else if ( b.containsField( "$regex" ) ) { + o = Pattern.compile( (String)b.get( "$regex" ), + BSON.regexFlags( (String)b.get( "$options" )) ); + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } else if ( b.containsField( "$ts" ) ) { + Long ts = ((Number)b.get("$ts")).longValue(); + Long inc = ((Number)b.get("$inc")).longValue(); + o = new BSONTimestamp(ts.intValue(), inc.intValue()); + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } else if ( b.containsField( "$code" ) ) { + if (b.containsField("$scope")) { + o = new CodeWScope((String)b.get("$code"), (DBObject)b.get("$scope")); + } else { + o = new Code((String)b.get("$code")); + } + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } else if ( b.containsField( "$ref" ) ) { + o = new DBRef(null, (String)b.get("$ref"), b.get("$id")); + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } else if ( b.containsField( "$minKey" ) ) { + o = new MinKey(); + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } else if ( b.containsField( "$maxKey" ) ) { + o = new MaxKey(); + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } else if ( b.containsField( "$uuid" ) ) { + o = UUID.fromString((String)b.get("$uuid")); + if (!isStackEmpty()) { + cur().put( name, o ); + } else { + setRoot(o); + } + } + } + return o; + } + + private boolean _lastArray = false; + + public static final String _msDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + public static final String _secDateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/JSONParseException.java b/src/com/massivecraft/mcore3/lib/mongodb/util/JSONParseException.java new file mode 100644 index 00000000..25c6ca8f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/JSONParseException.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +/** + * Exception throw when invalid JSON is passed to JSONParser. + * + * This exception creates a message that points to the first + * offending character in the JSON string: + *

    + * { "x" : 3, "y" : 4, some invalid json.... }
    + *                     ^
    + * 
    + */ +public class JSONParseException extends RuntimeException { + + private static final long serialVersionUID = -4415279469780082174L; + + String s; + int pos; + + public String getMessage() { + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append(s); + sb.append("\n"); + for(int i=0;iObjectSerializer instances that produce various flavors of + * JSON. + */ +public class JSONSerializers { + + private JSONSerializers() { + } + + /** + * Returns an ObjectSerializer that mostly conforms to the strict JSON format defined in + * getStrict in preference to this method. + * + * @return object serializer + * @see #getStrict() + */ + public static ObjectSerializer getLegacy() { + + ClassMapBasedObjectSerializer serializer = addCommonSerializers(); + + serializer.addObjectSerializer(Date.class, new LegacyDateSerializer(serializer)); + serializer.addObjectSerializer(BSONTimestamp.class, new LegacyBSONTimestampSerializer(serializer)); + serializer.addObjectSerializer(Binary.class, new LegacyBinarySerializer()); + serializer.addObjectSerializer(byte[].class, new LegacyBinarySerializer()); + return serializer; + } + + /** + * Returns an ObjectSerializer that conforms to the strict JSON format defined in + * "); + } + + } + + private static class ObjectArraySerializer extends CompoundObjectSerializer { + + ObjectArraySerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + buf.append("[ "); + for (int i = 0; i < Array.getLength(obj); i++) { + if (i > 0) + buf.append(" , "); + serializer.serialize(Array.get(obj, i), buf); + } + + buf.append("]"); + } + + } + + private static class ToStringSerializer extends AbstractObjectSerializer { + + @Override + public void serialize(Object obj, StringBuilder buf) { + buf.append(obj.toString()); + } + + } + + private static class LegacyBSONTimestampSerializer extends CompoundObjectSerializer { + + LegacyBSONTimestampSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + BSONTimestamp t = (BSONTimestamp) obj; + BasicDBObject temp = new BasicDBObject(); + temp.put("$ts", Integer.valueOf(t.getTime())); + temp.put("$inc", Integer.valueOf(t.getInc())); + serializer.serialize(temp, buf); + } + + } + + private static class CodeSerializer extends CompoundObjectSerializer { + + CodeSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + Code c = (Code) obj; + BasicDBObject temp = new BasicDBObject(); + temp.put("$code", c.getCode()); + serializer.serialize(temp, buf); + } + + } + + private static class CodeWScopeSerializer extends CompoundObjectSerializer { + + CodeWScopeSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + CodeWScope c = (CodeWScope) obj; + BasicDBObject temp = new BasicDBObject(); + temp.put("$code", c.getCode()); + temp.put("$scope", c.getScope()); + serializer.serialize(temp, buf); + } + + } + + private static class LegacyDateSerializer extends CompoundObjectSerializer { + + LegacyDateSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + Date d = (Date) obj; + SimpleDateFormat format = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + format.setCalendar(new GregorianCalendar( + new SimpleTimeZone(0, "GMT"))); + serializer.serialize( + new BasicDBObject("$date", format.format(d)), + buf); + } + + } + + private static class DBObjectSerializer extends CompoundObjectSerializer { + + DBObjectSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + boolean first = true; + buf.append("{ "); + DBObject dbo = (DBObject) obj; + String name; + + for (final String s : dbo.keySet()) { + name = s; + + if (first) + first = false; + else + buf.append(" , "); + + JSON.string(buf, name); + buf.append(" : "); + serializer.serialize(dbo.get(name), buf); + } + + buf.append("}"); + } + + } + + private static class DBRefBaseSerializer extends CompoundObjectSerializer { + + DBRefBaseSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + DBRefBase ref = (DBRefBase) obj; + BasicDBObject temp = new BasicDBObject(); + temp.put("$ref", ref.getRef()); + temp.put("$id", ref.getId()); + serializer.serialize(temp, buf); + } + + } + + private static class IterableSerializer extends CompoundObjectSerializer { + + IterableSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @SuppressWarnings("rawtypes") + @Override + public void serialize(Object obj, StringBuilder buf) { + boolean first = true; + buf.append("[ "); + + for (final Object o : ((Iterable) obj)) { + if (first) + first = false; + else + buf.append(" , "); + + serializer.serialize(o, buf); + } + buf.append("]"); + } + } + + private static class MapSerializer extends CompoundObjectSerializer { + + MapSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @SuppressWarnings("rawtypes") + @Override + public void serialize(Object obj, StringBuilder buf) { + boolean first = true; + buf.append("{ "); + Map m = (Map) obj; + Entry entry; + + for (final Object o : m.entrySet()) { + entry = (Entry) o; + if (first) + first = false; + else + buf.append(" , "); + JSON.string(buf, entry.getKey().toString()); + buf.append(" : "); + serializer.serialize(entry.getValue(), buf); + } + + buf.append("}"); + } + + } + + private static class MaxKeySerializer extends CompoundObjectSerializer { + + MaxKeySerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + serializer.serialize(new BasicDBObject("$maxKey", + 1), buf); + } + + } + + private static class MinKeySerializer extends CompoundObjectSerializer { + + MinKeySerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + serializer.serialize(new BasicDBObject("$minKey", + 1), buf); + } + + } + + private static class ObjectIdSerializer extends CompoundObjectSerializer { + + ObjectIdSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + serializer.serialize( + new BasicDBObject("$oid", obj.toString()), buf); + } + } + + private static class PatternSerializer extends CompoundObjectSerializer { + + PatternSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + DBObject externalForm = new BasicDBObject(); + externalForm.put("$regex", obj.toString()); + if (((Pattern) obj).flags() != 0) + externalForm.put("$options", + Bytes.regexFlags(((Pattern) obj).flags())); + serializer.serialize(externalForm, buf); + } + } + + private static class StringSerializer extends AbstractObjectSerializer { + + @Override + public void serialize(Object obj, StringBuilder buf) { + JSON.string(buf, (String) obj); + } + } + + private static class UUIDSerializer extends CompoundObjectSerializer { + + UUIDSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + UUID uuid = (UUID) obj; + BasicDBObject temp = new BasicDBObject(); + temp.put("$uuid", uuid.toString()); + serializer.serialize(temp, buf); + } + } + + private static class BSONTimestampSerializer extends CompoundObjectSerializer { + + BSONTimestampSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + BSONTimestamp t = (BSONTimestamp) obj; + BasicDBObject temp = new BasicDBObject(); + temp.put("$t", Integer.valueOf(t.getTime())); + temp.put("$i", Integer.valueOf(t.getInc())); + BasicDBObject timestampObj = new BasicDBObject(); + timestampObj.put("$timestamp", temp); + serializer.serialize(timestampObj, buf); + } + + } + + private static class DateSerializer extends CompoundObjectSerializer { + + DateSerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + Date d = (Date) obj; + serializer.serialize( + new BasicDBObject("$date", d.getTime()), buf); + } + + } + + private abstract static class BinarySerializerBase extends CompoundObjectSerializer { + BinarySerializerBase(ObjectSerializer serializer) { + super(serializer); + } + + protected void serialize(byte[] bytes, byte type, StringBuilder buf) { + DBObject temp = new BasicDBObject(); + temp.put("$binary", + (new Base64Codec()).encode(bytes)); + temp.put("$type", type); + serializer.serialize(temp, buf); + } + } + + private static class BinarySerializer extends BinarySerializerBase { + BinarySerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + Binary bin = (Binary) obj; + serialize(bin.getData(), bin.getType(), buf); + } + + } + + private static class ByteArraySerializer extends BinarySerializerBase { + ByteArraySerializer(ObjectSerializer serializer) { + super(serializer); + } + + @Override + public void serialize(Object obj, StringBuilder buf) { + serialize((byte[]) obj, (byte) 0, buf); + } + + } +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/MyAsserts.java b/src/com/massivecraft/mcore3/lib/mongodb/util/MyAsserts.java new file mode 100644 index 00000000..582fef8d --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/MyAsserts.java @@ -0,0 +1,203 @@ +// MyAsserts.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.Arrays; +import java.util.regex.Pattern; + +public class MyAsserts { + + public static class MyAssert extends RuntimeException { + + private static final long serialVersionUID = -4415279469780082174L; + + MyAssert( String s ){ + super( s ); + _s = s; + } + + public String toString(){ + return _s; + } + + final String _s; + } + + public static void assertTrue( boolean b ){ + if ( ! b ) + throw new MyAssert( "false" ); + } + + public static void assertTrue( boolean b , String msg ){ + if ( ! b ) + throw new MyAssert( "false : " + msg ); + } + + public static void assertFalse( boolean b ){ + if ( b ) + throw new MyAssert( "true" ); + } + + public static void assertEquals( int a , int b ){ + if ( a != b ) + throw new MyAssert( "" + a + " != " + b ); + } + + public static void assertEquals( long a , long b ){ + if ( a != b ) + throw new MyAssert( "" + a + " != " + b ); + } + + public static void assertEquals( char a , char b ){ + if ( a != b ) + throw new MyAssert( "" + a + " != " + b ); + } + + public static void assertEquals( short a , short b ){ + if ( a != b ) + throw new MyAssert( "" + a + " != " + b ); + } + + public static void assertEquals( byte expected , byte result ) { + if ( expected != result ) + throw new MyAssert( "" + expected + " != " + result ); + } + + public static void assertEquals( double a , double b , double diff ){ + if ( Math.abs( a - b ) > diff ) + throw new MyAssert( "" + a + " != " + b ); + } + + public static void assertEquals( String a , Object b ){ + _assertEquals( a , b == null ? null : b.toString() ); + } + + public static void assertEquals( Object a , Object b ){ + _assertEquals( a , b ); + } + + public static void _assertEquals( Object a , Object b ){ + if ( a == null ){ + if ( b == null ) + return; + throw new MyAssert( "left null, right not" ); + } + + if ( a.equals( b ) ) + return; + + throw new MyAssert( "[" + a + "] != [" + b + "] " ); + } + + public static void assertEquals( String a , String b , String msg ){ + if ( a.equals( b ) ) + return; + + throw new MyAssert( "[" + a + "] != [" + b + "] " + msg ); + } + + public static void assertArrayEquals(byte[] expected, byte[] result) { + if (Arrays.equals(expected, result)) + return; + + throw new MyAssert("These arrays are different, but they might be big so not printing them here"); + } + + public static void assertNotEquals( Object a , Object b ){ + if ( a == null ){ + if ( b != null ) + return; + throw new MyAssert( "left null, right null" ); + } + + if ( ! a.equals( b ) ) + return; + + throw new MyAssert( "[" + a + "] == [" + b + "] " ); + } + + public static void assertClose( String a , Object o){ + assertClose( a , o == null ? "null" : o.toString() ); + } + + public static void assertClose( String a , String b ){ + assertClose(a, b, ""); + } + + public static void assertClose( String a , String b, String tag ){ + + if (isClose(a, b)) { + return; + } + + throw new MyAssert( tag + "[" + a + "] != [" + b + "]" ); + } + + public static boolean isClose(String a, String b) { + a = _simplify( a ); + b = _simplify( b ); + return a.equalsIgnoreCase(b); + } + + private static String _simplify( String s ){ + s = s.trim(); + s = _whiteSpace.matcher( s ).replaceAll( "" ); + return s; + } + + private static Pattern _whiteSpace = Pattern.compile( "\\s+" , Pattern.DOTALL | Pattern.MULTILINE ); + + public static void assertNull( Object foo ){ + if ( foo == null ) + return; + + throw new MyAssert( "not null [" + foo + "]" ); + } + + public static void assertNotNull( Object foo ){ + if ( foo != null ) + return; + + throw new MyAssert( "null" ); + } + + public static void assertLess( long lower , long higher ){ + if ( lower < higher ) + return; + + throw new MyAssert( lower + " is higher than " + higher ); + } + + public static void assertLess( double lower , double higher ){ + if ( lower < higher ) + return; + + throw new MyAssert( lower + " is higher than " + higher ); + } + + public static void assertEmptyString( String s ) { + if( !s.equals( "" ) ) + throw new MyAssert( s ); + } + + public static void fail(String errorMessage) { + throw new MyAssert(errorMessage); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/ObjectSerializer.java b/src/com/massivecraft/mcore3/lib/mongodb/util/ObjectSerializer.java new file mode 100644 index 00000000..496539f2 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/ObjectSerializer.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2012 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +/** + * Interface describing methods for serializing an object to a string. + */ +public interface ObjectSerializer { + /** + * Serializes obj into buf. + * + * @param obj object to serialize + * @param buf buffer to serialize into + */ + void serialize(Object obj, StringBuilder buf); + + /** + * Serializes obj. + * @param obj object to serialize + * @return the serialized object + */ + String serialize(Object obj); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/OptionMap.java b/src/com/massivecraft/mcore3/lib/mongodb/util/OptionMap.java new file mode 100644 index 00000000..f0dbe2d6 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/OptionMap.java @@ -0,0 +1,31 @@ +// OptionMap.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.TreeMap; + +public class OptionMap extends TreeMap { + + private static final long serialVersionUID = -4415279469780082174L; + + public int getInt( String name , int def ){ + return StringParseUtil.parseIfInt( get( name ) , def ); + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/SimplePool.java b/src/com/massivecraft/mcore3/lib/mongodb/util/SimplePool.java new file mode 100644 index 00000000..2924babf --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/SimplePool.java @@ -0,0 +1,360 @@ +// SimplePool.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.DynamicMBean; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanInfo; + +public abstract class SimplePool implements DynamicMBean { + + static final boolean TRACK_LEAKS = Boolean.getBoolean( "MONGO-TRACKLEAKS" ); + static final long _sleepTime = 2; + + /** + * See full constructor docs + */ + public SimplePool( String name , int maxToKeep , int maxTotal ){ + this( name , maxToKeep , maxTotal , false , false ); + } + + /** Initializes a new pool of objects. + * @param name name for the pool + * @param maxToKeep max to hold to at any given time. if < 0 then no limit + * @param maxTotal max to have allocated at any point. if there are no more, get() will block + * @param trackLeaks if leaks should be tracked + */ + public SimplePool( String name , int maxToKeep , int maxTotal , boolean trackLeaks , boolean debug ){ + _name = name; + _maxToKeep = maxToKeep; + _maxTotal = maxTotal; + _trackLeaks = trackLeaks || TRACK_LEAKS; + _debug = debug; + _mbeanInfo = new MBeanInfo( this.getClass().getName() , _name , + new MBeanAttributeInfo[]{ + new MBeanAttributeInfo( "name" , "java.lang.String" , "name of pool" , true , false , false ) , + new MBeanAttributeInfo( "size" , "java.lang.Integer" , "total size of pool" , true , false , false ) , + new MBeanAttributeInfo( "available" , "java.lang.Integer" , "total connections available" , true , false , false ) , + new MBeanAttributeInfo( "inUse" , "java.lang.Integer" , "number connections in use right now" , true , false , false ) , + new MBeanAttributeInfo( "everCreated" , "java.lang.Integer" , "number connections ever created" , true , false , false ) + } , null , null , null ); + + } + + /** Creates a new object of this pool's type. + * @return the new object. + */ + protected abstract T createNew(); + + /** + * callback to determine if an object is ok to be added back to the pool or used + * will be called when something is put back into the queue and when it comes out + * @return true if the object is ok to be added back to pool + */ + public boolean ok( T t ){ + return true; + } + + /** + * override this if you need to do any cleanup + */ + public void cleanup( T t ){} + + /** + * @return >= 0 the one to use, -1 don't use any + */ + protected int pick( int iThink , boolean couldCreate ){ + return iThink; + } + + /** + * call done when you are done with an object form the pool + * if there is room and the object is ok will get added + * @param t Object to add + */ + public void done( T t ){ + done( t , ok( t ) ); + } + + void done( T t , boolean ok ){ + if ( _trackLeaks ){ + synchronized ( _where ){ + _where.remove( _hash( t ) ); + } + } + + if ( ! ok ){ + synchronized ( _avail ){ + _all.remove( t ); + } + return; + } + + synchronized ( _avail ){ + if ( _maxToKeep < 0 || _avail.size() < _maxToKeep ){ + for ( int i=0; i<_avail.size(); i++ ) + if ( _avail.get( i ) == t ) + throw new RuntimeException( "trying to put something back in the pool that's already there" ); + + // if all doesn't contain it, it probably means this was cleared, so we don't want it + if ( _all.contains( t ) ){ + _avail.add( t ); + _waiting.release(); + } + } + else { + cleanup( t ); + } + } + } + + public void remove( T t ){ + done( t , false ); + } + + /** Gets an object from the pool - will block if none are available + * @return An object from the pool + */ + public T get(){ + return get(-1); + } + + /** Gets an object from the pool - will block if none are available + * @param waitTime + * negative - forever + * 0 - return immediately no matter what + * positive ms to wait + * @return An object from the pool + */ + public T get( long waitTime ){ + final T t = _get( waitTime ); + if ( t != null ){ + if ( _trackLeaks ){ + Throwable stack = new Throwable(); + stack.fillInStackTrace(); + synchronized ( _where ){ + _where.put( _hash( t ) , stack ); + } + } + } + return t; + } + + private int _hash( T t ){ + return System.identityHashCode( t ); + } + + private T _get( long waitTime ){ + long totalSlept = 0; + while ( true ){ + synchronized ( _avail ){ + + boolean couldCreate = _maxTotal <= 0 || _all.size() < _maxTotal; + + while ( _avail.size() > 0 ){ + int toTake = _avail.size() - 1; + toTake = pick( toTake, couldCreate ); + if ( toTake >= 0 ){ + T t = _avail.remove( toTake ); + if ( ok( t ) ){ + _debug( "got an old one" ); + return t; + } + _debug( "old one was not ok" ); + _all.remove( t ); + continue; + } + else if ( ! couldCreate ) { + throw new IllegalStateException( "can't pick nothing if can't create" ); + } + break; + } + + if ( couldCreate ){ + _everCreated++; + T t = createNew(); + _all.add( t ); + return t; + } + + if ( _trackLeaks && _trackPrintCount++ % 200 == 0 ){ + _wherePrint(); + _trackPrintCount = 1; + } + } + + if ( waitTime == 0 ) + return null; + + if ( waitTime > 0 && totalSlept >= waitTime ) + return null; + + long start = System.currentTimeMillis(); + try { + _waiting.tryAcquire( _sleepTime , TimeUnit.MILLISECONDS ); + } + catch ( InterruptedException ie ){ + } + + totalSlept += ( System.currentTimeMillis() - start ); + + } + } + + private void _wherePrint(){ + StringBuilder buf = new StringBuilder( toString() ).append( " waiting \n" ); + synchronized ( _where ){ + for ( Throwable t : _where.values() ){ + buf.append( "--\n" ); + final StackTraceElement[] st = t.getStackTrace(); + for ( int i=0; i getAll(){ + return _all.getAll().iterator(); + } + + public int available(){ + if ( _maxTotal <= 0 ) + throw new IllegalStateException( "this pool has an infinite number of things available" ); + return _maxTotal - inUse(); + } + + public int everCreated(){ + return _everCreated; + } + + private void _debug( String msg ){ + if( _debug ) + System.out.println( "SimplePool [" + _name + "] : " + msg ); + } + + public int maxToKeep(){ + return _maxToKeep; + } + + public Object getAttribute(String attribute){ + if ( attribute.equals( "name" ) ) + return _name; + if ( attribute.equals( "size" ) ) + return _maxToKeep; + if ( attribute.equals( "available" ) ) + return available(); + if ( attribute.equals( "inUse" ) ) + return inUse(); + if ( attribute.equals( "everCreated" ) ) + return _everCreated; + + System.err.println( "com.mongo.util.SimplePool unknown attribute: " + attribute ); + throw new RuntimeException( "unknown attribute: " + attribute ); + } + + public AttributeList getAttributes(String[] attributes){ + AttributeList l = new AttributeList(); + for ( int i=0; i _avail = new ArrayList(); + protected final List _availSafe = Collections.unmodifiableList( _avail ); + private final WeakBag _all = new WeakBag(); + private final Map _where = new HashMap(); + + private final Semaphore _waiting = new Semaphore(0); + + private int _everCreated = 0; + private int _trackPrintCount = 0; + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/StringBuilderPool.java b/src/com/massivecraft/mcore3/lib/mongodb/util/StringBuilderPool.java new file mode 100644 index 00000000..cf9805e3 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/StringBuilderPool.java @@ -0,0 +1,55 @@ +// StringBuilderPool.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +public class StringBuilderPool extends SimplePool { + + /** Initializes a pool of a given number of StringBuilders, each of a certain size. + * @param maxToKeep the number of string builders in the pool + * @param maxSize the size of each string builder + */ + public StringBuilderPool( String name , int maxToKeep , int maxSize ){ + super( "StringBuilderPool-" + name , maxToKeep , -1 ); + _maxSize = maxSize; + } + + /** Create a new string builder. + * @return the string builder + */ + public StringBuilder createNew(){ + return new StringBuilder(); + } + + /** Checks that the given string builder is within the size limit. + * @param buf the builder to check + * @return if it is not too big + */ + public boolean ok( StringBuilder buf ){ + if ( buf.length() > _maxSize ) + return false; + buf.setLength( 0 ); + return true; + } + + protected long memSize( StringBuilder buf ){ + return buf.length() * 2; + } + + final int _maxSize; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/StringParseUtil.java b/src/com/massivecraft/mcore3/lib/mongodb/util/StringParseUtil.java new file mode 100644 index 00000000..a863a9bc --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/StringParseUtil.java @@ -0,0 +1,267 @@ +// StringParseUtil.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +public final class StringParseUtil { + + /** Turns a string into a boolean value and returns a default value if unsuccessful. + * @param s the string to convert + * @param d the default value + * @return equivalent boolean value + */ + public static boolean parseBoolean( String s , boolean d ){ + + if ( s == null ) + return d; + + s = s.trim(); + if ( s.length() == 0 ) + return d; + + char c = s.charAt( 0 ); + + if ( c == 't' || c == 'T' || + c == 'y' || c == 'Y' ) + return true; + + if ( c == 'f' || c == 'F' || + c == 'n' || c == 'N' ) + return false; + + return d; + } + + /** Turns a string into an int and returns a default value if unsuccessful. + * @param s the string to convert + * @param def the default value + * @return the int value + */ + public static int parseInt( String s , int def ){ + return parseInt( s , def , null , true ); + } + + /** Turns a string into an int using a given radix. + * @param s the string to convert + * @param radix radix to use + * @return the int value + */ + @SuppressWarnings("unused") + public static Number parseIntRadix( String s , int radix ){ + if ( s == null ) + return Double.NaN; + + s = s.trim(); + if ( s.length() == 0 ) + return Double.NaN; + + int firstDigit = -1; + int i = 0; + if ( s.charAt( 0 ) == '-' ) + i = 1; + // Find first non-digit. + for ( ; i 0; + if ( useLastIdx ) + lastIdx[0] = -1; + + if ( s == null ) + return def; + + s = s.trim(); + if ( s.length() == 0 ) + return def; + + int firstDigit = -1; + for ( int i=0; i 0 && s.charAt( firstDigit - 1 ) == '-' ) + firstDigit--; + + if ( useLastIdx ) + lastIdx[0] = lastDigit; + return Integer.parseInt( s.substring( firstDigit , lastDigit ) ); + } + + /** Turns a string into a Number and returns a default value if unsuccessful. + * @param s the string to convert + * @param def the default value + * @return the numeric value + */ + public static Number parseNumber( String s , Number def ){ + if ( s == null ) + return def; + + s = s.trim(); + if ( s.length() == 0) + return def; + + + int firstDigit = -1; + for ( int i=0; i 0 && s.charAt( firstDigit - 1 ) == '.' ){ + firstDigit--; + isDouble = true; + } + + if ( firstDigit > 0 && s.charAt( firstDigit - 1 ) == '-' ) + firstDigit--; + + if ( lastDigit < s.length() && s.charAt( lastDigit ) == '.' ){ + lastDigit++; + while ( lastDigit < s.length() && Character.isDigit( s.charAt( lastDigit ) ) ) + lastDigit++; + + isDouble = true; + } + + if ( lastDigit < s.length() && s.charAt( lastDigit ) == 'E' ){ + lastDigit++; + while ( lastDigit < s.length() && Character.isDigit( s.charAt( lastDigit ) ) ) + lastDigit++; + + isDouble = true; + } + + + final String actual = s.substring( firstDigit , lastDigit ); + + if ( isDouble || actual.length() > 17 ) + return Double.valueOf( actual ); + + + if ( actual.length() > 10 ) + return Long.valueOf( actual ); + + return Integer.valueOf( actual ); + } + + /** Use Java's "strict parsing" methods Integer.parseInt and Double.parseDouble to parse s "strictly". i.e. if it's neither a double or an integer, fail. + * @param s the string to convert + * @return the numeric value + */ + public static Number parseStrict( String s ){ + if( s.length() == 0 ) + return 0; + if( s.charAt(0) == '+' ) + s = s.substring( 1 ); + + if( s.matches( "(\\+|-)?Infinity" ) ) { + if( s.startsWith( "-" ) ) { + return Double.NEGATIVE_INFINITY; + } + else { + return Double.POSITIVE_INFINITY; + } + } + else if( s.indexOf('.') != -1 || + s.equals( "-0" ) ) { + return Double.valueOf(s); + } + // parse hex + else if( s.toLowerCase().indexOf( "0x" ) > -1 ) { + int coef = s.charAt( 0 ) == '-' ? -1 : 1; + if( s.length() > 17 ) + throw new RuntimeException( "Can't handle a number this big: "+s ); + // if coef == -1: (coef * -.5 + 2.5) == 3 + // e.g., -0xf00 (start substring at 3) + // if coef == 1: (coef * -.5 + 2.5) == 2 + // e.g., 0xf00 (start substring at 2) + if( s.length() > 9 ) + return coef * Long.valueOf( s.substring( (int)(coef * -.5 + 2.5) ) , 16 ); + return coef * Integer.valueOf( s.substring( (int)(coef * -.5 + 2.5) ) , 16 ); + } + + int e = s.toLowerCase().indexOf( 'e' ); + // parse exp + if( e > 0 ) { + double num = Double.parseDouble( s.substring( 0, e ) ); + int exp = Integer.parseInt( s.substring( e + 1 ) ); + return num * Math.pow( 10 , exp ); + } + + // parse with smallest possible precision + if ( s.length() > 17 ) + return Double.valueOf( s ); + else if ( s.length() > 9 ) + return Long.valueOf(s); + return Integer.valueOf(s); + } + + public static int parseIfInt( String s , int def ){ + if ( s == null || s.length() == 0 ) + return def; + + s = s.trim(); + + for ( int i=0; i { + + /** Initializes a new thread pool with a given name and number of threads. + * @param name identifying name + * @param numThreads the number of threads allowed in the pool + */ + public ThreadPool( String name , int numThreads ){ + this( name , numThreads , Integer.MAX_VALUE ); + } + + /** Initializes a new thread pool with a given name, number of threads, and queue size. + * @param name identifying name + * @param numThreads the number of threads allowed in the pool + * @param maxQueueSize the size of the pool entry queue + */ + public ThreadPool( String name , int numThreads , int maxQueueSize ){ + _name = name; + _maxThreads = numThreads; + _queue = new LinkedBlockingQueue( maxQueueSize ); + _myThreadGroup = new MyThreadGroup(); + _threads.add( new MyThread() ); + } + + /** Handles a given object. + * @param t the object to handle + * @throws Exception + */ + public abstract void handle( T t ) + throws Exception ; + + /** Handles a given object and exception. + * @param t the object to handle + * @param e the exception to handle + */ + public abstract void handleError( T t , Exception e ); + + /** Returns the size of the pool's queue. + * @return pool size + */ + public int queueSize(){ + return _queue.size(); + } + + /** Adds a new object to the pool, if possible. + * @param t the object to be added + * @return if the object was successfully added + */ + public boolean offer( T t ){ + if ( ( _queue.size() > 0 || _inProgress.get() == _threads.size() ) && + _threads.size() < _maxThreads ) + _threads.add( new MyThread() ); + return _queue.offer( t ); + } + + public int inProgress(){ + return _inProgress.get(); + } + + public int numThreads(){ + return _threads.size(); + } + + class MyThreadGroup extends ThreadGroup { + MyThreadGroup(){ + super( "ThreadPool.MyThreadGroup:" + _name ); + } + + public void uncaughtException( Thread t, Throwable e ){ + for ( int i=0; i<_threads.size(); i++ ){ + if ( _threads.get( i ) == t ){ + _threads.remove( i ); + break; + } + } + } + } + + class MyThread extends Thread { + MyThread(){ + super( _myThreadGroup , "ThreadPool.MyThread:" + _name + ":" + _threads.size() ); + setDaemon( true ); + start(); + } + + public void run(){ + while ( true ){ + T t = null; + + try { + t = _queue.take(); + } + catch ( InterruptedException ie ){ + } + + if ( t == null ) + continue; + + try { + _inProgress.incrementAndGet(); + handle( t ); + } + catch ( Exception e ){ + handleError( t , e ); + } + finally { + _inProgress.decrementAndGet(); + } + } + } + } + + final String _name; + final int _maxThreads; + + private final AtomicInteger _inProgress = new AtomicInteger(0); + private final List _threads = new Vector(); + private final BlockingQueue _queue; + private final MyThreadGroup _myThreadGroup; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/ThreadUtil.java b/src/com/massivecraft/mcore3/lib/mongodb/util/ThreadUtil.java new file mode 100644 index 00000000..0344690f --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/ThreadUtil.java @@ -0,0 +1,76 @@ +// ThreadUtil.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ThreadUtil { + + /** Creates an prints a stack trace */ + public static void printStackTrace(){ + Exception e = new Exception(); + e.fillInStackTrace(); + e.printStackTrace(); + } + + /** Pauses for a given number of milliseconds + * @param time number of milliseconds for which to pause + */ + public static void sleep( long time ){ + try { + Thread.sleep( time ); + } + catch ( InterruptedException e ){ + } + } + + public static void pushStatus( String what ){ + pushStatus( Thread.currentThread() , what ); + } + + public static void pushStatus( Thread t , String what ){ + getStatus( t ).push( what ); + } + + public static void clearStatus(){ + clearStatus( Thread.currentThread() ); + } + + public static void clearStatus( Thread t ){ + getStatus( t ).clear(); + } + + public static FastStack getStatus(){ + return getStatus( Thread.currentThread() ); + } + + public static FastStack getStatus( Thread t ){ + FastStack s = _threads.get( t.getId() ); + if ( s == null ){ + s = new FastStack(); + _threads.put( t.getId() , s ); + } + return s; + } + + private static final Map> _threads = Collections.synchronizedMap( new HashMap>() ); + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/TimeConstants.java b/src/com/massivecraft/mcore3/lib/mongodb/util/TimeConstants.java new file mode 100644 index 00000000..1fc2c236 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/TimeConstants.java @@ -0,0 +1,39 @@ +// TimeConstants.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +public class TimeConstants { + + public static final long MS_MILLISECOND = 1; + public static final long MS_SECOND = 1000; + public static final long MS_MINUTE = MS_SECOND * 60; + public static final long MS_HOUR = MS_MINUTE * 60; + public static final long MS_DAY = MS_HOUR * 24; + public static final long MS_WEEK = MS_DAY * 7; + public static final long MS_MONTH = MS_WEEK * 4; + public static final long MS_YEAR = MS_DAY * 365; + + public static final long S_SECOND = 1; + public static final long S_MINUTE = 60 * S_SECOND; + public static final long S_HOUR = 60 * S_MINUTE; + public static final long S_DAY = 24 * S_HOUR; + public static final long S_WEEK = 7 * S_DAY; + public static final long S_MONTH = 30 * S_DAY; + public static final long S_YEAR = S_DAY * 365; +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/UniqueList.java b/src/com/massivecraft/mcore3/lib/mongodb/util/UniqueList.java new file mode 100644 index 00000000..b32bb03b --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/UniqueList.java @@ -0,0 +1,41 @@ +// UniqueList.java + +/** + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.util.ArrayList; +import java.util.Collection; + +public class UniqueList extends ArrayList { + + private static final long serialVersionUID = -4415279469780082174L; + + public boolean add( T t ){ + if ( contains( t ) ) + return false; + return super.add( t ); + } + + public boolean addAll(Collection c) { + boolean added = false; + for ( T t : c ) + added = added || add( t ); + return added; + } + +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/Util.java b/src/com/massivecraft/mcore3/lib/mongodb/util/Util.java new file mode 100644 index 00000000..36ccbc68 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/Util.java @@ -0,0 +1,72 @@ +/** + * + * Copyright (C) 2008 10gen Inc. + * + * 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.massivecraft.mcore3.lib.mongodb.util; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Misc utility helpers. Not sure what else to call the class + */ +public class Util { + + public static String toHex( byte b[] ){ + StringBuilder sb = new StringBuilder(); + + for ( int i=0; i implements Iterable { + + /** Initializes a new weak bag. */ + public WeakBag(){ + } + + /** Adds an element to the bag. + * @param t Element to add + */ + public void add( T t ){ + _refs.add( new MyRef( t ) ); + } + + public boolean remove( T t ){ + + for ( Iterator i = _refs.iterator(); i.hasNext(); ){ + MyRef ref = i.next(); + if( ref == null ) + continue; + T me = ref.get(); + + if ( me == null ){ + // this is just here cause i'm already doing the work, so why not + i.remove(); + continue; + } + + if ( me == t ){ + i.remove(); + return true; + } + } + return false; + } + + public boolean contains( T t ){ + + for ( Iterator i = _refs.iterator(); i.hasNext(); ){ + MyRef ref = i.next(); + T me = ref.get(); + if ( me == t ) + return true; + } + return false; + } + + /** Returns the size of the bag. + * @return the size of the bag + */ + public int size(){ + clean(); + return _refs.size(); + } + + /** Removes all object from the bag. */ + public void clear(){ + _refs.clear(); + } + + /** Removes any null objects from the bag. */ + public void clean(){ + for ( Iterator i = _refs.iterator(); i.hasNext(); ){ + MyRef ref = i.next(); + if ( ref.get() == null ) + i.remove(); + } + } + + public Iterator iterator(){ + return getAll().iterator(); + } + + public List getAll(){ + + List l = new ArrayList(); + + for ( Iterator i = _refs.iterator(); i.hasNext(); ){ + MyRef ref = i.next(); + T t = ref.get(); + if ( t == null ) + i.remove(); + else + l.add( t ); + } + + return l; + } + + class MyRef extends WeakReference { + MyRef( T t ){ + super( t ); + } + } + + private final List _refs = new LinkedList(); +} diff --git a/src/com/massivecraft/mcore3/lib/mongodb/util/package.html b/src/com/massivecraft/mcore3/lib/mongodb/util/package.html new file mode 100644 index 00000000..10d79bb7 --- /dev/null +++ b/src/com/massivecraft/mcore3/lib/mongodb/util/package.html @@ -0,0 +1,3 @@ + +

    Package containing misc utils.

    + diff --git a/src/com/massivecraft/mcore3/mongodb/InventoryTypeAdapter.java b/src/com/massivecraft/mcore3/mongodb/InventoryTypeAdapter.java new file mode 100644 index 00000000..66ca129d --- /dev/null +++ b/src/com/massivecraft/mcore3/mongodb/InventoryTypeAdapter.java @@ -0,0 +1,76 @@ +package com.massivecraft.mcore3.mongodb; + +import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import com.massivecraft.mcore3.lib.mongodb.BasicDBObject; + +public class InventoryTypeAdapter +{ + // -------------------------------------------- // + // FIELD NAME CONSTANTS + // -------------------------------------------- // + + public static final String SIZE = "size"; + + // -------------------------------------------- // + // STATIC LOGIC + // -------------------------------------------- // + + public static BasicDBObject serialize(Inventory src) + { + BasicDBObject bsonInventory = new BasicDBObject(); + ItemStack[] itemStacks = src.getContents(); + bsonInventory.put(SIZE, itemStacks.length); + + for (int i = 0; i < itemStacks.length; i++) + { + ItemStack itemStack = itemStacks[i]; + BasicDBObject bsonItemStack = ItemStackAdapter.serialize(itemStack); + if (bsonItemStack == null) continue; + bsonInventory.put(String.valueOf(i), bsonItemStack); + } + + return bsonInventory; + } + + public static Inventory deserialize(BasicDBObject bsonInventory) + { + if ( ! bsonInventory.containsField(SIZE)) return null; + int size = bsonInventory.getInt(SIZE); + + ItemStack[] itemStacks = new ItemStack[size]; + + for (int i = 0; i < size; i++) + { + // Fetch the jsonItemStack or mark it as empty and continue + String stackIdx = String.valueOf(i); + BasicDBObject bsonItemStack = (BasicDBObject) bsonInventory.get(stackIdx); + ItemStack itemStack = ItemStackAdapter.deserialize(bsonItemStack); + itemStacks[i] = itemStack; + } + + Inventory ret = new CraftInventoryCustom(null, size, "items"); + ret.setContents(itemStacks); + return ret; + } + + // -------------------------------------------- // + // UTIL + // -------------------------------------------- // + + // This utility is nice to have in many cases :) + public static boolean isInventoryEmpty(Inventory inv) + { + if (inv == null) return true; + for (ItemStack stack : inv.getContents()) + { + if (stack == null) continue; + if (stack.getAmount() == 0) continue; + if (stack.getTypeId() == 0) continue; + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/com/massivecraft/mcore3/mongodb/ItemStackAdapter.java b/src/com/massivecraft/mcore3/mongodb/ItemStackAdapter.java new file mode 100644 index 00000000..2959b99c --- /dev/null +++ b/src/com/massivecraft/mcore3/mongodb/ItemStackAdapter.java @@ -0,0 +1,99 @@ +package com.massivecraft.mcore3.mongodb; + +import java.util.Map.Entry; + +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; + +import com.massivecraft.mcore3.lib.mongodb.BasicDBObject; + +public class ItemStackAdapter +{ + // -------------------------------------------- // + // FIELD NAME CONSTANTS + // -------------------------------------------- // + + public static final String TYPE = "type"; + public static final String AMOUNT = "amount"; + public static final String DAMAGE = "damage"; + public static final String ENCHANTMENTS = "enchantments"; + + // -------------------------------------------- // + // STATIC LOGIC + // -------------------------------------------- // + + public static BasicDBObject serialize(ItemStack itemStack) + { + if (itemStack == null || itemStack.getTypeId() == 0 || itemStack.getAmount() == 0) + { + return null; + } + + BasicDBObject bsonItemStack = new BasicDBObject(); + + bsonItemStack.put(TYPE, itemStack.getTypeId()); + + if (itemStack.getAmount() != 1) + { + bsonItemStack.put(AMOUNT, itemStack.getAmount()); + } + if (itemStack.getDurability() != 0) // Durability is a weird name since it is the amount of damage. + { + bsonItemStack.put(DAMAGE, itemStack.getDurability()); + } + if (itemStack.getEnchantments().size() > 0) + { + BasicDBObject bsonEnchantments = new BasicDBObject(); + for (Entry entry : itemStack.getEnchantments().entrySet()) + { + bsonEnchantments.put(String.valueOf(entry.getKey().getId()), entry.getValue()); + } + bsonItemStack.put(ENCHANTMENTS, bsonEnchantments); + } + + return bsonItemStack; + } + + public static ItemStack deserialize(BasicDBObject bsonItemStack) + { + if (bsonItemStack == null) return null; + + // Populate values + int type = 0; + int amount = 1; + short damage = 0; + + if (bsonItemStack.containsField(TYPE)) + { + type = bsonItemStack.getInt(TYPE); + } + + if (bsonItemStack.containsField(AMOUNT)) + { + amount = bsonItemStack.getInt(AMOUNT); + } + + if (bsonItemStack.containsField(DAMAGE)) + { + damage = (short) bsonItemStack.getInt(DAMAGE); + } + + // Create Non enchanted stack + ItemStack stack = new ItemStack(type, amount, damage); + + // Add enchantments if there are any + if (bsonItemStack.containsField(ENCHANTMENTS)) + { + BasicDBObject bsonEnchantments = (BasicDBObject) bsonItemStack.get(ENCHANTMENTS); + + for (Entry enchantmentEntry: bsonEnchantments.entrySet()) + { + int enchantmentId = Integer.valueOf(enchantmentEntry.getKey()); + Integer enchantmentLevel = (Integer) enchantmentEntry.getValue(); + stack.addUnsafeEnchantment(Enchantment.getById(enchantmentId), enchantmentLevel); + } + } + + return stack; + } +} diff --git a/src/com/massivecraft/mcore3/util/DiscUtil.java b/src/com/massivecraft/mcore3/util/DiscUtil.java index 31b6bcc9..052a539d 100644 --- a/src/com/massivecraft/mcore3/util/DiscUtil.java +++ b/src/com/massivecraft/mcore3/util/DiscUtil.java @@ -26,6 +26,8 @@ public class DiscUtil } in.close(); + + if (ret.length() == 0) return ret; return ret.substring(0, ret.length()-1); } @@ -75,4 +77,19 @@ public class DiscUtil { return downloadUrl(urlstring, new File(filename)); } + + public static boolean deleteRecursive(File path) throws FileNotFoundException + { + if ( ! path.exists()) throw new FileNotFoundException(path.getAbsolutePath()); + boolean ret = true; + if (path.isDirectory()) + { + for (File f : path.listFiles()) + { + ret = ret && deleteRecursive(f); + } + } + return ret && path.delete(); + } + } diff --git a/src/com/massivecraft/mcore3/util/PlayerUtil.java b/src/com/massivecraft/mcore3/util/PlayerUtil.java index db1ff976..a7d12bce 100644 --- a/src/com/massivecraft/mcore3/util/PlayerUtil.java +++ b/src/com/massivecraft/mcore3/util/PlayerUtil.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import net.minecraft.server.DedicatedServer; import net.minecraft.server.EntityPlayer; import net.minecraft.server.Packet8UpdateHealth; @@ -20,7 +21,9 @@ public class PlayerUtil public static void populateAllVisitorNames() { // Find the player folder - String levelName = ((CraftServer)Bukkit.getServer()).getServer().propertyManager.getString("level-name", "world"); + CraftServer cserver = (CraftServer)Bukkit.getServer(); + DedicatedServer dserver = (DedicatedServer)cserver.getServer(); + String levelName = dserver.propertyManager.getString("level-name", "world"); File playerfolder = new File(Bukkit.getWorldContainer(), new File(levelName, "players").getPath()); // Populate by removing .dat @@ -36,7 +39,7 @@ public class PlayerUtil { CraftPlayer cplayer = (CraftPlayer)player; EntityPlayer eplayer = cplayer.getHandle(); - eplayer.netServerHandler.sendPacket(new Packet8UpdateHealth(eplayer.getHealth(), eplayer.getFoodData().a(), eplayer.getFoodData().c())); + eplayer.netServerHandler.sendPacket(new Packet8UpdateHealth(eplayer.getHealth(), eplayer.getFoodData().a(), eplayer.getFoodData().e())); } @SuppressWarnings("unchecked") diff --git a/src/com/massivecraft/mcore3/util/SmokeUtil.java b/src/com/massivecraft/mcore3/util/SmokeUtil.java index 376480ea..bf286553 100644 --- a/src/com/massivecraft/mcore3/util/SmokeUtil.java +++ b/src/com/massivecraft/mcore3/util/SmokeUtil.java @@ -1,9 +1,9 @@ package com.massivecraft.mcore3.util; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; +import java.util.List; import java.util.Random; -import java.util.Set; import net.minecraft.server.ChunkPosition; import net.minecraft.server.MinecraftServer; @@ -12,8 +12,6 @@ import net.minecraft.server.Packet60Explosion; import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.block.Block; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; @@ -96,51 +94,17 @@ public class SmokeUtil // Fake Explosion ======== public static void fakeExplosion(Location location) { - fakeExplosion(location, 4); - } - - public static void fakeExplosion(Location location, int radius) - { - if (location == null) return; - World world = location.getWorld(); - Set blocks = new HashSet(); - int r2 = radius * radius; - for(int x = -radius; x <= radius; x++) - { - for(int y = -radius; y <= radius; y++) - { - for(int z = -radius; z <= radius; z++) - { - if(x*x + y*y + z*z + x+y+z + 0.75 > r2) continue; - Block toadd = world.getBlockAt((int)(location.getX()+x+0.5), (int)(location.getY()+x+0.5), (int)(location.getZ()+x+0.5)); - if (toadd == null) continue; - if (toadd.getTypeId() != 0) continue; - blocks.add(toadd); - } - } - } - fakeExplosion(location, blocks); - } - - protected static void fakeExplosion(Location location, Set blocks) - { - if (blocks == null) return; - if (blocks.size() == 0) return; - - HashSet chunkPositions = new HashSet(blocks.size()); - - for (Block block : blocks) - { - chunkPositions.add(new ChunkPosition(block.getX(), block.getY(), block.getZ())); - } - - Packet60Explosion packet = new Packet60Explosion(location.getX(),location.getY(), location.getZ(), 0.1f, chunkPositions); - CraftServer craftServer = (CraftServer) Bukkit.getServer(); - MinecraftServer minecraftServer = craftServer.getServer(); - - minecraftServer.serverConfigurationManager.sendPacketNearby(location.getX(), location.getY(), location.getZ(), 64, ((CraftWorld)location.getWorld()).getHandle().dimension, packet); + fakeExplosion(location, (Bukkit.getViewDistance()+1)*16*2); } + public static void fakeExplosion(Location location, int viewDistance) + { + List chunkPositions = new ArrayList(); + Packet60Explosion packet = new Packet60Explosion(location.getX(),location.getY(), location.getZ(), 0.1f, chunkPositions, null); + CraftServer craftServer = (CraftServer) Bukkit.getServer(); + MinecraftServer minecraftServer = craftServer.getServer(); + minecraftServer.getServerConfigurationManager().sendPacketNearby(location.getX(), location.getY(), location.getZ(), viewDistance, ((CraftWorld)location.getWorld()).getHandle().dimension, packet); + } // -------------------------------------------- // // Attach continuous effects to or locations