// 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.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.mongodb.DBApiLayer.Result; import com.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: *
* { "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(){
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.mongodb.WriteConcern concern ){
return command( concern.getCommand() );
}
/**
* @see {@link DB#getLastError(com.mongodb.WriteConcern) }
* @param w
* @param wtimeout
* @param fsync
* @return The command result
* @throws MongoException
*/
public CommandResult getLastError( int w , int wtimeout , boolean fsync ){
return command( (new com.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.mongodb.WriteConcern concern ){
if (concern == null) throw new IllegalArgumentException();
_concern = concern;
}
/**
* Gets the write concern for this database.
* @return
*/
public com.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(){
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 ){
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 ){
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
* @throws MongoException
*/
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
* @throws MongoException
*/
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
* @throws MongoException
*/
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; iresetError()
*
* 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(){
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(){
command(new BasicDBObject("reseterror", 1));
}
/**
* For testing purposes only - this method forces an error to help test error handling
* @throws MongoException
*/
public void forceError(){
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.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 );
final Mongo _mongo;
final String _name;
protected boolean _readOnly = false;
private com.mongodb.WriteConcern _concern;
private com.mongodb.ReadPreference _readPref;
final Bytes.OptionHolder _options;
String _username;
byte[] _authhash = null;
}