001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.kernel;
020:
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Modifier;
023: import java.util.ArrayList;
024: import java.util.Arrays;
025: import java.util.Collection;
026: import java.util.Collections;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031:
032: import org.apache.commons.collections.map.LinkedMap;
033: import org.apache.commons.lang.StringUtils;
034: import org.apache.openjpa.lib.rop.ListResultObjectProvider;
035: import org.apache.openjpa.lib.rop.RangeResultObjectProvider;
036: import org.apache.openjpa.lib.rop.ResultObjectProvider;
037: import org.apache.openjpa.lib.util.Localizer;
038: import org.apache.openjpa.meta.ClassMetaData;
039: import org.apache.openjpa.util.Exceptions;
040: import org.apache.openjpa.util.ImplHelper;
041: import org.apache.openjpa.util.OpenJPAException;
042: import org.apache.openjpa.util.UserException;
043:
044: /**
045: * A query that is executed by a user-defined method.
046: *
047: * @author Abe White
048: * @nojavadoc
049: */
050: public class MethodStoreQuery extends AbstractStoreQuery {
051:
052: public static final String LANGUAGE = QueryLanguages.LANG_METHODQL;
053:
054: private static final Class[] ARGS_DATASTORE = new Class[] {
055: StoreContext.class, ClassMetaData.class, boolean.class,
056: Map.class, FetchConfiguration.class };
057: private static final Class[] ARGS_INMEM = new Class[] {
058: StoreContext.class, ClassMetaData.class, boolean.class,
059: Object.class, Map.class, FetchConfiguration.class };
060: private static final int OBJ_INDEX = 3;
061:
062: private static final Localizer _loc = Localizer
063: .forPackage(MethodStoreQuery.class);
064:
065: private LinkedMap _params = null;
066:
067: public void invalidateCompilation() {
068: if (_params != null)
069: _params.clear();
070: }
071:
072: public Executor newInMemoryExecutor(ClassMetaData meta, boolean subs) {
073: return new MethodExecutor(this , meta, subs, true);
074: }
075:
076: public Executor newDataStoreExecutor(ClassMetaData meta,
077: boolean subs) {
078: return new MethodExecutor(this , meta, subs, false);
079: }
080:
081: public boolean supportsInMemoryExecution() {
082: return true;
083: }
084:
085: public boolean supportsDataStoreExecution() {
086: return true;
087: }
088:
089: /**
090: * Parse the parameter declarations.
091: */
092: private LinkedMap bindParameterTypes() {
093: ctx.lock();
094: try {
095: if (_params != null)
096: return _params;
097: String params = ctx.getParameterDeclaration();
098: if (params == null)
099: return EMPTY_PARAMS;
100:
101: List decs = Filters.parseDeclaration(params, ',',
102: "parameters");
103: if (_params == null)
104: _params = new LinkedMap(
105: (int) (decs.size() / 2 * 1.33 + 1));
106: String name;
107: Class cls;
108: for (int i = 0; i < decs.size(); i += 2) {
109: name = (String) decs.get(i);
110: cls = ctx.classForName(name, null);
111: if (cls == null)
112: throw new UserException(_loc.get("bad-param-type",
113: name));
114: _params.put(decs.get(i + 1), cls);
115: }
116: return _params;
117: } finally {
118: ctx.unlock();
119: }
120: }
121:
122: /**
123: * Uses a user-defined method named by the filter string to execute the
124: * query.
125: */
126: private static class MethodExecutor extends AbstractExecutor
127: implements Executor {
128:
129: private final ClassMetaData _meta;
130: private final boolean _subs;
131: private final boolean _inMem;
132: private Method _meth = null;
133:
134: public MethodExecutor(MethodStoreQuery q,
135: ClassMetaData candidate, boolean subclasses,
136: boolean inMem) {
137: _meta = candidate;
138: _subs = subclasses;
139: _inMem = inMem;
140: }
141:
142: public ResultObjectProvider executeQuery(StoreQuery q,
143: Object[] params, Range range) {
144: // convert the parameters into a map
145: Map paramMap;
146: if (params.length == 0)
147: paramMap = Collections.EMPTY_MAP;
148: else {
149: Map paramTypes = q.getContext().getParameterTypes();
150: paramMap = new HashMap((int) (params.length * 1.33 + 1));
151: int idx = 0;
152: for (Iterator itr = paramTypes.keySet().iterator(); itr
153: .hasNext(); idx++)
154: paramMap.put(itr.next(), params[idx]);
155: }
156:
157: FetchConfiguration fetch = q.getContext()
158: .getFetchConfiguration();
159: StoreContext sctx = q.getContext().getStoreContext();
160: ResultObjectProvider rop;
161: Object[] args;
162: if (_inMem) {
163: args = new Object[] { sctx, _meta,
164: (_subs) ? Boolean.TRUE : Boolean.FALSE, null,
165: paramMap, fetch };
166:
167: Iterator itr = null;
168: Collection coll = q.getContext()
169: .getCandidateCollection();
170: if (coll == null) {
171: Extent ext = q.getContext().getQuery()
172: .getCandidateExtent();
173: itr = ext.iterator();
174: } else
175: itr = coll.iterator();
176:
177: List results = new ArrayList();
178: try {
179: Object obj;
180: while (itr.hasNext()) {
181: obj = itr.next();
182: if (obj == null
183: || !_meta.getDescribedType()
184: .isInstance(obj))
185: continue;
186:
187: args[OBJ_INDEX] = obj;
188: if (((Boolean) invoke(q, args)).booleanValue())
189: results.add(obj);
190: }
191: } finally {
192: ImplHelper.close(itr);
193: }
194: rop = new ListResultObjectProvider(results);
195: } else {
196: // datastore
197: args = new Object[] { sctx, _meta,
198: (_subs) ? Boolean.TRUE : Boolean.FALSE,
199: paramMap, fetch };
200: rop = (ResultObjectProvider) invoke(q, args);
201: }
202:
203: if (range.start != 0 || range.end != Long.MAX_VALUE)
204: rop = new RangeResultObjectProvider(rop, range.start,
205: range.end);
206: return rop;
207: }
208:
209: /**
210: * Invoke the internal method with the given arguments, returning the
211: * result.
212: */
213: private Object invoke(StoreQuery q, Object[] args) {
214: validate(q);
215: try {
216: return _meth.invoke(null, args);
217: } catch (OpenJPAException ke) {
218: throw ke;
219: } catch (Exception e) {
220: throw new UserException(_loc.get("method-error", _meth,
221: Exceptions.toString(Arrays.asList(args))), e);
222: }
223: }
224:
225: public void validate(StoreQuery q) {
226: if (_meth != null)
227: return;
228:
229: String methName = q.getContext().getQueryString();
230: if (StringUtils.isEmpty(methName))
231: throw new UserException(_loc.get("no-method"));
232:
233: int dotIdx = methName.lastIndexOf('.');
234: Class cls;
235: if (dotIdx == -1)
236: cls = _meta.getDescribedType();
237: else {
238: cls = q.getContext().classForName(
239: methName.substring(0, dotIdx), null);
240: if (cls == null)
241: throw new UserException(_loc.get(
242: "bad-method-class", methName.substring(0,
243: dotIdx), methName));
244: methName = methName.substring(dotIdx + 1);
245: }
246:
247: Method meth;
248: Class[] types = (_inMem) ? ARGS_INMEM : ARGS_DATASTORE;
249: try {
250: meth = cls.getMethod(methName, types);
251: } catch (Exception e) {
252: String msg = (_inMem) ? "bad-inmem-method"
253: : "bad-datastore-method";
254: throw new UserException(_loc.get(msg, methName, cls));
255: }
256: if (!Modifier.isStatic(meth.getModifiers()))
257: throw new UserException(_loc.get("method-not-static",
258: meth));
259: if (!ResultObjectProvider.class.isAssignableFrom(meth
260: .getReturnType()))
261: throw new UserException(_loc.get(
262: "method-return-type-invalid", meth, meth
263: .getReturnType()));
264: _meth = meth;
265: }
266:
267: public LinkedMap getParameterTypes(StoreQuery q) {
268: return ((MethodStoreQuery) q).bindParameterTypes();
269: }
270: }
271: }
|