001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019:
020: package org.apache.beehive.controls.system.jdbc.parser;
021:
022: import org.apache.beehive.controls.api.ControlException;
023: import org.apache.beehive.controls.api.context.ControlBeanContext;
024: import org.apache.beehive.controls.system.jdbc.TypeMappingsFactory;
025: import org.apache.beehive.controls.system.jdbc.JdbcControl;
026:
027: import java.lang.reflect.Method;
028: import java.util.Map;
029: import java.util.regex.Pattern;
030:
031: /**
032: * Represents a method parameter substitution into the SQL annotation's statement member. Delimited by '{' and '}'.
033: * Method parameter names must exactly match the name used in the SQL statement in order for the substitution to occur.
034: * <p/>
035: * <pre>
036: * SQL(statement="SELECT * FROM {tableName}")
037: * public void getAll(String tableName) throws SQLException;
038: * </pre>
039: */
040: public final class ReflectionFragment extends SqlFragment {
041:
042: private static final String PREPARED_STATEMENT_SUB_MARK = "?";
043: private static final Pattern s_parameterNamePattern = Pattern
044: .compile("\\.");
045:
046: private final String _parameterName;
047: private final String[] _nameQualifiers;
048: private int _sqlDataType;
049:
050: /**
051: * Create a new ReflectionFragment with the specifed method parameter name.
052: *
053: * @param parameterName The name of the parameter whose value should be substituted at this location.
054: */
055: protected ReflectionFragment(String parameterName) {
056: _parameterName = parameterName;
057: _sqlDataType = TypeMappingsFactory.TYPE_UNKNOWN;
058: _nameQualifiers = s_parameterNamePattern.split(_parameterName);
059: }
060:
061: /**
062: * Create a new ReflectionFragment with the specified method parameter name and SQL type.
063: *
064: * @param parameterName The name of the parameter whose value should be substituted at this location.
065: * @param sqlDataType A String specifing the SQL data type for this parameter.
066: */
067: protected ReflectionFragment(String parameterName,
068: String sqlDataType) {
069: this (parameterName);
070: if (sqlDataType != null) {
071: _sqlDataType = TypeMappingsFactory.getInstance()
072: .convertStringToSQLType(sqlDataType);
073: }
074: }
075:
076: /**
077: * Return text generated by this fragment for a PreparedStatement
078: *
079: * @param context A ControlBeanContext instance.
080: * @param m The annotated method.
081: * @param args The method's parameters
082: * @return Always returns a PREPARED_STATEMENT_SUB_MARK
083: */
084: protected String getPreparedStatementText(
085: ControlBeanContext context, Method m, Object[] args) {
086: // this reflection fragment may resolve to a JdbcControl.ComplexSqlFragment
087: // if so it changes the behavior a bit.
088: Object val = getParameterValue(context, m, args);
089: if (val instanceof JdbcControl.ComplexSqlFragment) {
090: return ((JdbcControl.ComplexSqlFragment) val).getSQL();
091: } else {
092: return PREPARED_STATEMENT_SUB_MARK;
093: }
094: }
095:
096: /**
097: * A reflection fragment may evaluate to an JdbcControl.ComplexSqlFragment type,
098: * which requires additional steps to evaluate after reflection.
099: *
100: * @param context Control bean context.
101: * @param m Method.
102: * @param args Method args.
103: * @return true or false.
104: */
105: protected boolean hasComplexValue(ControlBeanContext context,
106: Method m, Object[] args) {
107: Object val = getParameterValue(context, m, args);
108: return val instanceof JdbcControl.ComplexSqlFragment;
109: }
110:
111: /**
112: * Always true for ReflectionFragment.
113: *
114: * @return true
115: */
116: protected boolean hasParamValue() {
117: return true;
118: }
119:
120: /**
121: * Get the parameter name (as specified in the SQL statement).
122: *
123: * @return The parameter name.
124: */
125: protected String getParameterName() {
126: return _parameterName;
127: }
128:
129: /**
130: * Get a copy of the array of parameter name qualifiers.
131: *
132: * @return An array of parameter name qualifiers.
133: */
134: protected String[] getParameterNameQualifiers() {
135: String[] nameQualifiersCopy = new String[_nameQualifiers.length];
136: System.arraycopy(_nameQualifiers, 0, nameQualifiersCopy, 0,
137: _nameQualifiers.length);
138: return nameQualifiersCopy;
139: }
140:
141: /**
142: * Get the SQL data type of this param.
143: *
144: * @return The SQL data type for this param.
145: */
146: protected int getParamSqlDataType() {
147: return _sqlDataType;
148: }
149:
150: /**
151: * For JUnit testing.
152: *
153: * @return The String value of this fragment.
154: */
155: public String toString() {
156: return PREPARED_STATEMENT_SUB_MARK;
157: }
158:
159: /**
160: * Get the value of this parameter.
161: *
162: * @param context ControlBeanContext instance to evaluate the parameter's value against.
163: * @param method Method instance to evaluate against.
164: * @param args Method argument values
165: * @return All parameter object values contained within this fragment
166: */
167: protected Object[] getParameterValues(ControlBeanContext context,
168: Method method, Object[] args) {
169:
170: Object value = getParameterValue(context, method, args);
171: if (value instanceof JdbcControl.ComplexSqlFragment) {
172: JdbcControl.SQLParameter[] params = ((JdbcControl.ComplexSqlFragment) value)
173: .getParameters();
174: Object[] values = new Object[params.length];
175: for (int i = 0; i < params.length; i++) {
176: values[i] = params[i].value;
177: }
178: return values;
179: }
180: return new Object[] { value };
181: }
182:
183: //
184: // /////////////////////////////////////////////// PRIVATE METHODS /////////////////////////////////////////////
185: //
186:
187: /**
188: * Get the value from the method param.
189: * @param method
190: * @param args
191: * @return Value of reflected method param.
192: */
193: private Object getParameterValue(ControlBeanContext context,
194: Method method, Object[] args) {
195: Object value;
196: try {
197: value = context.getParameterValue(method,
198: _nameQualifiers[0], args);
199: } catch (IllegalArgumentException iae) {
200: throw new ControlException(
201: "Invalid argument name in SQL statement: "
202: + _nameQualifiers[0], iae);
203: }
204:
205: for (int i = 1; i < _nameQualifiers.length; i++) {
206: // handle maps, properties, and fields...
207: value = extractValue(value, _nameQualifiers[i - 1],
208: _nameQualifiers[i]);
209: }
210: return value;
211: }
212:
213: /**
214: * Get the value from the referenced method parameter using java reflection
215: *
216: * @param aValue
217: * @param aName
218: * @param bName
219: * @return The value
220: */
221: private Object extractValue(Object aValue, String aName,
222: String bName) {
223:
224: Class aClass = aValue.getClass();
225: Object value;
226:
227: //
228: // a.isB() or a.getB()
229: //
230: String bNameCapped = Character.toUpperCase(bName.charAt(0))
231: + bName.substring(1);
232: Method getMethod = null;
233: Class retType;
234:
235: //
236: // try a.isB() first, if found verify that a.isB() returns a boolean value,
237: // and that there is not also a a.getB() method - if there is except
238: //
239: try {
240:
241: getMethod = aClass.getMethod("is" + bNameCapped,
242: (Class[]) null);
243: retType = getMethod.getReturnType();
244: if (!(retType.equals(Boolean.class) || retType
245: .equals(Boolean.TYPE))) {
246: // only boolean returns can be isB()
247: getMethod = null;
248: } else {
249: /**
250: * make sure ("get" + bNameCapped) does not exist as well
251: * see CR216159
252: */
253: boolean getMethodFound = true;
254: try {
255: aClass.getMethod("get" + bNameCapped,
256: (Class[]) null);
257: } catch (NoSuchMethodException e) {
258: getMethodFound = false;
259: }
260:
261: if (getMethodFound) {
262: throw new ControlException(
263: "Colliding field accsessors in user defined class '"
264: + aClass.getName()
265: + "' for field '"
266: + bName
267: + "'. Please use is<FieldName> for boolean fields and get<FieldName> name for other datatypes.");
268: }
269: }
270: } catch (NoSuchMethodException e) {
271: // ignore
272: }
273:
274: //
275: // try a.getB() if a.isB() was not found.
276: //
277: if (getMethod == null) {
278: try {
279: getMethod = aClass.getMethod("get" + bNameCapped,
280: (Class[]) null);
281: } catch (NoSuchMethodException e) {
282: // ignore
283: }
284: }
285:
286: if (getMethod != null) {
287: // OK- a.getB()
288: try {
289: value = getMethod.invoke(aValue, (Object[]) null);
290: } catch (IllegalAccessException e) {
291: throw new ControlException(
292: "Unable to access public method: "
293: + e.toString());
294: } catch (java.lang.reflect.InvocationTargetException e) {
295: throw new ControlException(
296: "Exception thrown when executing : "
297: + getMethod.getName()
298: + "() to use as parameter");
299: }
300: return value;
301: }
302:
303: //
304: // try a.b
305: //
306:
307: try {
308: value = aClass.getField(bName).get(aValue);
309: return value;
310: } catch (NoSuchFieldException e) {
311: // ignore
312: } catch (IllegalAccessException e) {
313: // ignore
314: }
315:
316: //
317: // try a.get(b)
318: //
319:
320: if (aValue instanceof Map) {
321: try {
322: value = TypeMappingsFactory.getInstance().lookupType(
323: aValue, new Object[] { bName });
324: return value;
325: } catch (Exception mapex) {
326: throw new ControlException(
327: "Exception thrown when executing Map.get() to resolve parameter"
328: + mapex.toString());
329: }
330: }
331:
332: // no other options...
333: throw new ControlException(
334: "Illegal argument in SQL statement: "
335: + _parameterName
336: + "; unable to find suitable method of retrieving property "
337: + bName + " out of object " + aName + ".");
338: }
339: }
|