/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.translator.mongodb; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; import org.teiid.core.types.BlobImpl; import org.teiid.core.types.BlobType; import org.teiid.core.types.InputStreamFactory; import org.teiid.core.types.Streamable; import org.teiid.core.util.ReflectionHelper; import org.teiid.language.Argument; import org.teiid.language.Command; import org.teiid.language.Literal; import org.teiid.language.visitor.SQLStringVisitor; import org.teiid.metadata.RuntimeMetadata; import org.teiid.mongodb.MongoDBConnection; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.ExecutionContext; import org.teiid.translator.ProcedureExecution; import org.teiid.translator.TranslatorException; import com.mongodb.Cursor; import com.mongodb.DBCollection; import com.mongodb.DBObject; import com.mongodb.WriteResult; import com.mongodb.util.JSON; /** * This enables the Direct Query execution of the MongoDB queries. For that to happen the procedure * invocation needs to be like * {code} * native('CollectionName;{$match : { \"CompanyName\" : \"A\"}};{$project : {\"CompanyName\", "userName}}', [param1, param2]) * {code} * the first parameter must be collection name, then the arguments must match the aggregation pipeline structure delimited by * semi-colon (;) between each pipeline statement * * select x.* from TABLE(call native('city;{$match:{"city":"FREEDOM"}}')) t, xmltable('/city' PASSING JSONTOXML('city', cast(array_get(t.tuple, 1) as BLOB)) COLUMNS city string, state string) x * * select JSONTOXML('city', cast(array_get(t.tuple, 1) as BLOB)) as col from TABLE(call native('city;{$match:{"city":"FREEDOM"}}')) t; */ public class MongoDBDirectQueryExecution extends MongoDBBaseExecution implements ProcedureExecution { private String query; private List<Argument> arguments; protected boolean returnsArray; private Cursor results; private MongoDBExecutionFactory executionFactory; public MongoDBDirectQueryExecution(List<Argument> arguments, @SuppressWarnings("unused") Command cmd, ExecutionContext executionContext, RuntimeMetadata metadata, MongoDBConnection connection, String nativeQuery, boolean returnsArray, MongoDBExecutionFactory ef) { super(executionContext, metadata, connection); this.arguments = arguments; this.returnsArray = returnsArray; this.query = nativeQuery; this.executionFactory = ef; } @Override public void execute() throws TranslatorException { StringBuilder buffer = new StringBuilder(); SQLStringVisitor.parseNativeQueryParts(query, arguments, buffer, new SQLStringVisitor.Substitutor() { @Override public void substitute(Argument arg, StringBuilder builder, int index) { Literal argumentValue = arg.getArgumentValue(); builder.append(argumentValue.getValue()); } }); StringTokenizer st = new StringTokenizer(buffer.toString(), ";"); //$NON-NLS-1$ String collectionName = st.nextToken(); boolean shellOperation = collectionName.equalsIgnoreCase("$ShellCmd"); //$NON-NLS-1$ String shellOperationName = null; if (shellOperation) { collectionName = st.nextToken(); shellOperationName = st.nextToken(); } DBCollection collection = this.mongoDB.getCollection(collectionName); if (collection == null) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18020, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18020, collectionName)); } if (shellOperation) { ArrayList<Object> operations = new ArrayList<Object>(); while (st.hasMoreTokens()) { String token = st.nextToken(); if (token.startsWith("{")) { //$NON-NLS-1$ operations.add(JSON.parse(token)); } else { operations.add(token); } } try { ReflectionHelper helper = new ReflectionHelper(DBCollection.class); Method method = helper.findBestMethodOnTarget(shellOperationName, operations.toArray(new Object[operations.size()])); Object result = method.invoke(collection, operations.toArray(new Object[operations.size()])); if (result instanceof Cursor) { this.results = (Cursor)result; } else if (result instanceof WriteResult) { WriteResult wr = (WriteResult)result; if (wr.getError() != null) { throw new TranslatorException(wr.getError()); } } } catch (NoSuchMethodException e) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18034, e, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18034, buffer.toString())); } catch (SecurityException e) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18034, e, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18034, buffer.toString())); } catch (IllegalAccessException e) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18034, e, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18034, buffer.toString())); } catch (IllegalArgumentException e) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18034, e, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18034, buffer.toString())); } catch (InvocationTargetException e) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18034, e.getTargetException(), MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18034, buffer.toString())); } } else { ArrayList<DBObject> operations = new ArrayList<DBObject>(); while (st.hasMoreTokens()) { String token = st.nextToken(); operations.add((DBObject)JSON.parse(token)); } if (operations.isEmpty()) { throw new TranslatorException(MongoDBPlugin.Event.TEIID18021, MongoDBPlugin.Util.gs(MongoDBPlugin.Event.TEIID18021, collectionName)); } this.results = collection.aggregate(operations, this.executionFactory.getOptions(this.executionContext.getBatchSize())); } } @Override public List<?> getOutputParameterValues() throws TranslatorException { return null; } @Override public List<?> next() throws TranslatorException, DataNotAvailableException { final DBObject value = nextRow(); if (value == null) { return null; } BlobType result = new BlobType(new BlobImpl(new InputStreamFactory() { @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(JSON.serialize(value).getBytes(Streamable.CHARSET)); } })); if (returnsArray) { List<Object[]> row = new ArrayList<Object[]>(1); row.add(new Object[] {result}); return row; } return Arrays.asList(result); } @SuppressWarnings("unused") public DBObject nextRow() throws TranslatorException, DataNotAvailableException { if (this.results != null && this.results.hasNext()) { DBObject result = this.results.next(); return result; } return null; } @Override public void close() { if (this.results != null) { this.results.close(); this.results = null; } } @Override public void cancel() throws TranslatorException { close(); } }