/******************************************************************************* * * Copyright (c) 2012 GigaSpaces Technologies Ltd. All rights reserved * * 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 org.openspaces.jpa.openjpa; import java.io.IOException; import java.io.StreamTokenizer; import java.io.StringReader; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.openjpa.kernel.AbstractStoreQuery; import org.apache.openjpa.kernel.QueryContext; import org.apache.openjpa.kernel.StoreContext; import org.apache.openjpa.kernel.StoreQuery; import org.apache.openjpa.lib.rop.ListResultObjectProvider; import org.apache.openjpa.lib.rop.ResultObjectProvider; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.util.GeneralException; import org.apache.openjpa.util.UserException; import org.openspaces.core.executor.DistributedTask; import org.openspaces.core.executor.Task; import org.openspaces.core.executor.internal.ExecutorMetaDataProvider; import org.openspaces.core.executor.internal.InternalDistributedSpaceTaskWrapper; import org.openspaces.core.executor.internal.InternalSpaceTaskWrapper; import org.openspaces.jpa.StoreManager; import org.openspaces.remoting.scripting.Script; import org.openspaces.remoting.scripting.ScriptingExecutor; import com.gigaspaces.async.AsyncFuture; import com.gigaspaces.executor.SpaceTask; import com.gigaspaces.internal.client.spaceproxy.ISpaceProxy; import com.gigaspaces.internal.utils.CollectionUtils; import com.j_spaces.core.client.SQLQuery; /** * Executes native SQLQueries and task * * @author anna * @since 8.0.1 * */ public class StoreManagerSQLQuery extends AbstractStoreQuery { private static final long serialVersionUID = 1L; private StoreManager _store; public StoreManagerSQLQuery(StoreManager store) { this._store = store; } public StoreManager getStore() { return _store; } public boolean supportsParameterDeclarations() { return false; } public boolean supportsDataStoreExecution() { return true; } public Executor newDataStoreExecutor(ClassMetaData meta, boolean subclasses) { return new SQLExecutor(); } public boolean requiresCandidateType() { return false; } public boolean requiresParameterDeclarations() { return false; } /** * Executes the filter as a SQL query. */ protected static class SQLExecutor extends AbstractExecutor { private final String _executeCommand = "execute ?"; private final ExecutorMetaDataProvider _executorMetaDataProvider = new ExecutorMetaDataProvider(); public SQLExecutor() { } public int getOperation(StoreQuery q) { return (q.getContext().getCandidateType() != null || q.getContext().getResultType() != null || q.getContext().getResultMappingName() != null || q.getContext().getResultMappingScope() != null) ? OP_SELECT : OP_UPDATE; } public ResultObjectProvider executeQuery(StoreQuery storeQuery, Object[] params, Range range) { // If candidate type was not set => task execution if (storeQuery.getContext().getCandidateType() == null) { return executeTaskOrScript(storeQuery, params); // Otherwise, SQLQuery execution } else { return executeSqlQuery(storeQuery, params); } } /** * Executes a GigaSpaces {@link SQLQuery} syntax query. * * @param storeQuery The query execute was called for. * @param params The query's parameters. * @return Query execution result as a list. */ @SuppressWarnings({ "rawtypes", "deprecation", "unchecked" }) private ResultObjectProvider executeSqlQuery(StoreQuery storeQuery, Object[] params) { try { // Throw an exception for a maybe common user mistake final String sql = StringUtils.trimToNull(storeQuery.getContext().getQueryString()); if (_executeCommand.equalsIgnoreCase(sql)) throw new UserException( "When specifying a candidate type - SQLQuery syntax should be used. " + "For task execution use the entityManager.createNativeQuery(String sql) method."); final QueryContext context = storeQuery.getContext(); final Class<?> type = context.getCandidateType(); final SQLQuery sqlQuery = new SQLQuery(type, context.getQueryString()); if (params != null) { for (int i = 0; i < params.length; i++) { sqlQuery.setParameter(i + 1, params[i]); } } final StoreManagerSQLQuery query = (StoreManagerSQLQuery) storeQuery; final StoreManager store = (StoreManager) query.getStore(); final Object[] result = store.getConfiguration().getSpace().readMultiple( sqlQuery, store.getCurrentTransaction(), Integer.MAX_VALUE, store.getConfiguration().getReadModifier()); return new ListResultObjectProvider(CollectionUtils.toList(result)); } catch (Exception e) { throw new GeneralException(e); } } /** * Executes a GigaSpaces {@link Task} or {@link Script}. * Decision is made based on the provided first parameter's instance. * * @param storeQuery The query which execute was called for. * @param params Query execution parameters. * @return {@link Task} or {@link Script} execution result. */ @SuppressWarnings({ "rawtypes" }) private ResultObjectProvider executeTaskOrScript(StoreQuery storeQuery, Object[] params) { QueryContext ctx = storeQuery.getContext(); String sql = StringUtils.trimToNull(ctx.getQueryString()); // Task execution SQL string must be: "execute ?" if (!_executeCommand.equalsIgnoreCase(sql)) throw new UserException("Unsupported native query task/script execution syntax - " + sql); final StoreManagerSQLQuery query = (StoreManagerSQLQuery) storeQuery; if (params == null) throw new UserException("Execute task/script is not supported for non-parametrized query."); if (params.length != 1) throw new UserException("Illegal number of arguments <" + params.length + "> should be <1>."); if (params[0] instanceof Task) return executeTask((Task) params[0], query); if (params[0] instanceof Script) return executeScript((Script) params[0], query); throw new UserException("Illegal task/script execution parameter type - " + params[0].getClass().getName() + ". " + Task.class.getName() + " or " + Script.class.getName() + " is expected."); } /** * Executes the provided GigaSpaces dynamic {@link Script}. * Execution shouldn't be transactional and if it is, the transaction isn't passed to the script. * * @param script The {@link Script} to execute. * @param query The query which execute was called for. * @return {@link Script} execution returned value. */ private ResultObjectProvider executeScript(Script script, StoreManagerSQLQuery query) { try { final ScriptingExecutor<?> executor = query.getStore().getConfiguration().getScriptingExecutorProxy(); Object result = executor.execute(script); List<Object> resultList = new LinkedList<Object>(); resultList.add(result); return new ListResultObjectProvider(resultList); } catch (Exception e) { throw new GeneralException(e); } } /** * Executes the provided GigaSpaces {@link Task}. * This operation must be under a {@link StoreManager} transaction. * * @param task The {@link Task} to execute. * @param query The query which execute was called for. * @return {@link Task} execution returned value. */ private ResultObjectProvider executeTask(Task<?> task, final StoreManagerSQLQuery query) { // Make sure there's an active store transaction // since we assume the task is changing data within the space final StoreContext context = query.getStore().getContext(); context.getBroker().assertActiveTransaction(); context.beginStore(); final ISpaceProxy space = (ISpaceProxy) query.getStore().getConfiguration().getSpace(); // Get routing from annotation final Object routing = _executorMetaDataProvider.findRouting(task); try { SpaceTask<?> spaceTask; if( task instanceof DistributedTask) spaceTask = new InternalDistributedSpaceTaskWrapper((DistributedTask) task); else spaceTask = new InternalSpaceTaskWrapper(task, routing); AsyncFuture<?> future = space.execute(spaceTask, routing, query.getStore().getCurrentTransaction(), null); Object taskResult = future.get(); List resultList = new LinkedList(); resultList.add(taskResult); return new ListResultObjectProvider(resultList); } catch (Exception e) { throw new GeneralException(e); } } public String[] getDataStoreActions(StoreQuery q, Object[] params, Range range) { return new String[] { q.getContext().getQueryString() }; } public boolean isPacking(StoreQuery q) { return q.getContext().getCandidateType() == null; } /** * The given query is parsed to find the parameter tokens of the form <code>?n</code> which * is different than <code>?</code> tokens in actual SQL parameter tokens. These * <code>?n</code> style tokens are replaced in the query string by <code>?</code> tokens. * * During the token parsing, the ordering of the tokens is recorded. The given userParam * must contain parameter keys as Integer and the same Integers must appear in the tokens. * */ public Object[] toParameterArray(StoreQuery q, Map userParams) { if (userParams == null || userParams.isEmpty()) return StoreQuery.EMPTY_OBJECTS; String sql = q.getContext().getQueryString(); List<Integer> paramOrder = new ArrayList<Integer>(); try { sql = substituteParams(sql, paramOrder); } catch (IOException ex) { throw new UserException(ex.getLocalizedMessage()); } Object[] result = new Object[paramOrder.size()]; int idx = 0; for (Integer key : paramOrder) { if (!userParams.containsKey(key)) throw new UserException("Missing parameter " + key + " in " + sql); result[idx++] = userParams.get(key); } // modify original JPA-style SQL to proper SQL q.getContext().getQuery().setQuery(sql); return result; } } /** * Utility method to substitute '?num' for parameters in the given SQL statement, and fill-in * the order of the parameter tokens */ public static String substituteParams(String sql, List<Integer> paramOrder) throws IOException { // if there's no "?" parameter marker, then we don't need to // perform the parsing process if (sql.indexOf("?") == -1) return sql; paramOrder.clear(); StreamTokenizer tok = new StreamTokenizer(new StringReader(sql)); tok.resetSyntax(); tok.quoteChar('\''); tok.wordChars('0', '9'); tok.wordChars('?', '?'); StringBuilder buf = new StringBuilder(sql.length()); for (int ttype; (ttype = tok.nextToken()) != StreamTokenizer.TT_EOF;) { switch (ttype) { case StreamTokenizer.TT_WORD: // a token is a positional parameter if it starts with // a "?" and the rest of the token are all numbers if (tok.sval.startsWith("?")) { buf.append("?"); String pIndex = tok.sval.substring(1); if (pIndex.length() > 0) { paramOrder.add(Integer.valueOf(pIndex)); } else { // or nothing paramOrder.add(paramOrder.size() + 1); } } else buf.append(tok.sval); break; case '\'': buf.append('\''); if (tok.sval != null) { buf.append(tok.sval); buf.append('\''); } break; default: buf.append((char) ttype); } } return buf.toString(); } }