/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.usergrid.mongo.protocol;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.bson.BSONObject;
import org.bson.BasicBSONObject;
import org.bson.types.ObjectId;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferInputStream;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.usergrid.management.ApplicationInfo;
import org.apache.usergrid.management.UserInfo;
import org.apache.usergrid.mongo.MongoChannelHandler;
import org.apache.usergrid.mongo.commands.MongoCommand;
import org.apache.usergrid.mongo.query.MongoQueryParser;
import org.apache.usergrid.mongo.utils.BSONUtils;
import org.apache.usergrid.persistence.Entity;
import org.apache.usergrid.persistence.EntityManager;
import org.apache.usergrid.persistence.index.query.Query;
import org.apache.usergrid.persistence.Results;
import org.apache.usergrid.persistence.Schema;
import org.apache.usergrid.security.shiro.PrincipalCredentialsToken;
import org.apache.usergrid.security.shiro.utils.SubjectUtils;
import org.apache.usergrid.utils.MapUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.usergrid.persistence.index.query.Identifier;
import org.apache.usergrid.persistence.index.query.Query.Level;
import static org.apache.usergrid.utils.JsonUtils.toJsonMap;
import static org.apache.usergrid.utils.MapUtils.entry;
import static org.apache.usergrid.utils.MapUtils.map;
public class OpQuery extends OpCrud {
private static final Logger logger = LoggerFactory.getLogger( OpQuery.class );
int flags;
int numberToSkip;
int numberToReturn;
BSONObject query;
BSONObject returnFieldSelector;
static Set<String> operators = new HashSet<String>();
static {
operators.add( "all" );
operators.add( "and" );
operators.add( "elemMatch" );
operators.add( "exists" );
operators.add( "gt" );
operators.add( "gte" );
operators.add( "in" );
operators.add( "lt" );
operators.add( "lte" );
operators.add( "mod" );
operators.add( "ne" );
operators.add( "nin" );
operators.add( "nor" );
operators.add( "not" );
operators.add( "or" );
operators.add( "regex" );
operators.add( "size" );
operators.add( "type" );
operators.add( "where" );
}
public OpQuery() {
opCode = OP_QUERY;
}
public int getFlags() {
return flags;
}
public void setFlags( int flags ) {
this.flags = flags;
}
public int getNumberToSkip() {
return numberToSkip;
}
public void setNumberToSkip( int numberToSkip ) {
this.numberToSkip = numberToSkip;
}
public int getNumberToReturn() {
return numberToReturn;
}
public void setNumberToReturn( int numberToReturn ) {
this.numberToReturn = numberToReturn;
}
public BSONObject getQuery() {
return query;
}
public void setQuery( BSONObject query ) {
this.query = query;
}
public void setQuery( Map<?, ?> map ) {
query = new BasicBSONObject();
query.putAll( map );
}
public BSONObject getReturnFieldSelector() {
return returnFieldSelector;
}
public void setReturnFieldSelector( BSONObject returnFieldSelector ) {
this.returnFieldSelector = returnFieldSelector;
}
public void setReturnFieldSelector( Map<?, ?> map ) {
returnFieldSelector = new BasicBSONObject();
returnFieldSelector.putAll( map );
}
@Override
public void decode( ChannelBuffer buffer ) throws IOException {
super.decode( buffer );
flags = buffer.readInt();
fullCollectionName = readCString( buffer );
numberToSkip = buffer.readInt();
numberToReturn = buffer.readInt();
query = BSONUtils.decoder().readObject( new ChannelBufferInputStream( buffer ) );
if ( buffer.readable() ) {
returnFieldSelector = BSONUtils.decoder().readObject( new ChannelBufferInputStream( buffer ) );
logger.info( "found fieldSeclector: {}", returnFieldSelector );
}
}
@Override
public ChannelBuffer encode( ChannelBuffer buffer ) {
int l = 28; // 7 ints * 4 bytes
ByteBuffer fullCollectionNameBytes = getCString( fullCollectionName );
l += fullCollectionNameBytes.capacity();
ByteBuffer queryBytes = encodeDocument( query );
l += queryBytes.capacity();
ByteBuffer returnFieldSelectorBytes = encodeDocument( returnFieldSelector );
l += returnFieldSelectorBytes.capacity();
messageLength = l;
buffer = super.encode( buffer );
buffer.writeInt( flags );
buffer.writeBytes( fullCollectionNameBytes );
buffer.writeInt( numberToSkip );
buffer.writeInt( numberToReturn );
buffer.writeBytes( queryBytes );
buffer.writeBytes( returnFieldSelectorBytes );
return buffer;
}
/*
* (non-Javadoc)
*
* @see org.apache.usergrid.mongo.protocol.OpCrud#doOp()
*/
@Override
public OpReply doOp( MongoChannelHandler handler, ChannelHandlerContext ctx, MessageEvent messageEvent ) {
logger.debug( "In OpQuery.doOp with fullCollectionName: {}", fullCollectionName );
Subject currentUser = SubjectUtils.getSubject();
String collectionName = getCollectionName();
if ( "$cmd".equals( collectionName ) ) {
@SuppressWarnings("unchecked") String commandName = ( String ) MapUtils.getFirstKey( getQuery().toMap() );
if ( "authenticate".equals( commandName ) ) {
return handleAuthenticate( handler, getDatabaseName() );
}
if ( "getnonce".equals( commandName ) ) {
return handleGetnonce();
}
if ( !currentUser.isAuthenticated() ) {
return handleUnauthorizedCommand( messageEvent );
}
MongoCommand command = MongoCommand.getCommand( commandName );
if ( command != null ) {
logger.info( "found command {} from name {}", command.getClass().getName(), commandName );
return command.execute( handler, ctx, messageEvent, this );
}
else {
logger.info( "No command for " + commandName );
}
}
if ( !currentUser.isAuthenticated() ) {
return handleUnauthorizedQuery( messageEvent );
}
if ( "system.namespaces".equals( collectionName ) ) {
return handleListCollections( handler, getDatabaseName() );
}
if ( "system.users".equals( collectionName ) ) {
return handleListUsers();
}
return handleQuery( handler );
}
private OpReply handleAuthenticate( MongoChannelHandler handler, String databaseName ) {
logger.info( "Authenticating for database " + databaseName + "... " );
String name = ( String ) query.get( "user" );
String nonce = ( String ) query.get( "nonce" );
String key = ( String ) query.get( "key" );
UserInfo user = null;
try {
user = handler.getOrganizations().verifyMongoCredentials( name, nonce, key );
}
catch ( Exception e1 ) {
return handleAuthFails( this );
}
if ( user == null ) {
return handleAuthFails( this );
}
PrincipalCredentialsToken token =
PrincipalCredentialsToken.getFromAdminUserInfoAndPassword(
user, key, handler.getEmf().getManagementAppId() );
Subject subject = SubjectUtils.getSubject();
try {
subject.login( token );
}
catch ( AuthenticationException e2 ) {
return handleAuthFails( this );
}
OpReply reply = new OpReply( this );
reply.addDocument( map( "ok", 1.0 ) );
return reply;
}
private OpReply handleGetnonce() {
String nonce = String.format( "%04x", ( new Random() ).nextLong() );
OpReply reply = new OpReply( this );
reply.addDocument( map( entry( "nonce", nonce ), entry( "ok", 1.0 ) ) );
return reply;
}
private OpReply handleUnauthorizedCommand( MessageEvent e ) {
// { "assertion" : "unauthorized db:admin lock type:-1 client:127.0.0.1"
// , "assertionCode" : 10057 , "errmsg" : "db assertion failure" , "ok"
// : 0.0}
OpReply reply = new OpReply( this );
reply.addDocument( map( entry( "assertion",
"unauthorized db:" + getDatabaseName() + " lock type:-1 client:" + ( ( InetSocketAddress ) e
.getRemoteAddress() ).getAddress().getHostAddress() ), entry( "assertionCode", 10057 ),
entry( "errmsg", "db assertion failure" ), entry( "ok", 0.0 ) ) );
return reply;
}
private OpReply handleUnauthorizedQuery( MessageEvent e ) {
// { "$err" : "unauthorized db:test lock type:-1 client:127.0.0.1" ,
// "code" : 10057}
OpReply reply = new OpReply( this );
reply.addDocument( map( entry( "$err",
"unauthorized db:" + getDatabaseName() + " lock type:-1 client:" + ( ( InetSocketAddress ) e
.getRemoteAddress() ).getAddress().getHostAddress() ), entry( "code", 10057 ) ) );
return reply;
}
private OpReply handleAuthFails( OpQuery opQuery ) {
// { "errmsg" : "auth fails" , "ok" : 0.0}
OpReply reply = new OpReply( opQuery );
reply.addDocument( map( entry( "errmsg", "auth fails" ), entry( "ok", 0.0 ) ) );
return reply;
}
private OpReply handleListCollections( MongoChannelHandler handler, String databaseName ) {
logger.info( "Handling list collections for database {} ... ", databaseName );
Identifier id = Identifier.from( databaseName );
OpReply reply = new OpReply( this );
ApplicationInfo application = SubjectUtils.getApplication( id );
if ( application == null ) {
return reply;
}
EntityManager em = handler.getEmf().getEntityManager( application.getId() );
try {
Set<String> collections = em.getApplicationCollections();
for ( String colName : collections ) {
if ( Schema.isAssociatedEntityType( colName ) ) {
continue;
}
reply.addDocument( map( "name", String.format( "%s.%s", databaseName, colName ) ) );
reply.addDocument( map( "name", String.format( "%s.%s.$_id_", databaseName, colName ) ) );
}
// reply.addDocument(map("name", databaseName + ".system.indexes"));
}
catch ( Exception ex ) {
logger.error( "Unable to retrieve collections", ex );
}
return reply;
}
private OpReply handleListUsers() {
logger.info( "Handling list users for database {} ... ", getDatabaseName() );
OpReply reply = new OpReply( this );
return reply;
}
private OpReply handleQuery( MongoChannelHandler handler ) {
logger.info( "Handling a query... " );
OpReply reply = new OpReply( this );
ApplicationInfo application = SubjectUtils.getApplication( Identifier.from( getDatabaseName() ) );
if ( application == null ) {
return reply;
}
int count = getNumberToReturn();
if ( count <= 0 ) {
count = 30;
}
EntityManager em = handler.getEmf().getEntityManager( application.getId() );
try {
Results results = null;
Query q = MongoQueryParser.toNativeQuery( query, returnFieldSelector, numberToReturn );
if ( q != null ) {
results = em.searchCollection( em.getApplicationRef(), getCollectionName(), q );
}
else {
results = em.getCollection( em.getApplicationRef(), getCollectionName(), null, count,
Level.ALL_PROPERTIES, false );
}
if ( !results.isEmpty() ) {
for ( Entity entity : results.getEntities() ) {
Object savedId = entity.getProperty( "_id" );
Object mongoId = null;
//try to parse it into an ObjectId
if ( savedId == null ) {
mongoId = entity.getUuid();
}
else {
try {
mongoId = new ObjectId( savedId.toString() );
//it's not a mongo Id, use it as is
}
catch ( IllegalArgumentException iae ) {
mongoId = savedId;
}
}
reply.addDocument( map( entry( "_id", mongoId ), toJsonMap( entity ),
entry( Schema.PROPERTY_UUID, entity.getUuid().toString() ) ) );
}
}
}
catch ( Exception ex ) {
logger.error( "Unable to retrieve collections", ex );
}
return reply;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "OpQuery [flags=" + flags + ", numberToSkip=" + numberToSkip + ", numberToReturn=" + numberToReturn
+ ", query=" + query + ", returnFieldSelector=" + returnFieldSelector + ", fullCollectionName="
+ fullCollectionName + ", messageLength=" + messageLength + ", requestID=" + requestID + ", responseTo="
+ responseTo + ", opCode=" + opCode + "]";
}
}