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;
021:
022: import com.sun.mirror.apt.AnnotationProcessorEnvironment;
023: import com.sun.mirror.declaration.Declaration;
024: import com.sun.mirror.declaration.FieldDeclaration;
025: import com.sun.mirror.declaration.MethodDeclaration;
026: import com.sun.mirror.declaration.TypeDeclaration;
027: import com.sun.mirror.type.ArrayType;
028: import com.sun.mirror.type.DeclaredType;
029: import com.sun.mirror.type.InterfaceType;
030: import com.sun.mirror.type.MirroredTypeException;
031: import com.sun.mirror.type.PrimitiveType;
032: import com.sun.mirror.type.TypeMirror;
033: import com.sun.mirror.type.VoidType;
034: import org.apache.beehive.controls.api.ControlException;
035: import org.apache.beehive.controls.api.bean.ControlChecker;
036: import org.apache.beehive.controls.system.jdbc.parser.ParameterChecker;
037: import org.apache.beehive.controls.system.jdbc.parser.SqlParser;
038: import org.apache.beehive.controls.system.jdbc.parser.SqlStatement;
039:
040: import java.util.Collection;
041: import java.util.ResourceBundle;
042: import java.util.Locale;
043: import java.text.MessageFormat;
044:
045: /**
046: * Annotation checker for the JdbcControl. Invoked at compile time by the controls framework.
047: */
048: public class JdbcControlChecker implements ControlChecker {
049:
050: private Locale _locale;
051:
052: /**
053: * Invoked by the control build-time infrastructure to process a declaration of
054: * a control extension (ie, an interface annotated with @ControlExtension), or
055: * a field instance of a control type.
056: */
057: public void check(Declaration decl,
058: AnnotationProcessorEnvironment env) {
059:
060: _locale = Locale.getDefault();
061:
062: if (decl instanceof TypeDeclaration) {
063:
064: //
065: // Check method annotations
066: //
067: Collection<? extends MethodDeclaration> methods = ((TypeDeclaration) decl)
068: .getMethods();
069: for (MethodDeclaration method : methods) {
070: checkSQL(method, env);
071:
072: }
073: } else if (decl instanceof FieldDeclaration) {
074:
075: //
076: // NOOP
077: //
078: } else {
079:
080: //
081: // NOOP
082: //
083: }
084: }
085:
086: /**
087: * Check the SQL method annotation. Lots to check here, stop checking as soon as an error is found.
088: *
089: * @param method Method to check.
090: * @param env Processor env.
091: */
092: private void checkSQL(MethodDeclaration method,
093: AnnotationProcessorEnvironment env) {
094:
095: final JdbcControl.SQL methodSQL = method
096: .getAnnotation(JdbcControl.SQL.class);
097: if (methodSQL == null) {
098: return;
099: }
100:
101: //
102: // check for empty SQL statement member
103: //
104: if (methodSQL.statement() == null
105: || methodSQL.statement().length() == 0) {
106: env.getMessager().printError(
107: method.getPosition(),
108: getResourceString("jdbccontrol.empty.statement",
109: method.getSimpleName()));
110: return;
111: }
112:
113: //
114: // Make sure maxrows is not set to some negative number other than -1
115: //
116: int maxRows = methodSQL.maxRows();
117: if (maxRows < JdbcControl.MAXROWS_ALL) {
118: env.getMessager().printError(
119: method.getPosition(),
120: getResourceString("jdbccontrol.bad.maxrows", method
121: .getSimpleName(), maxRows));
122: return;
123: }
124:
125: //
126: // Make sure maxArrayLength is not set to some negative number
127: //
128: int arrayMax = methodSQL.arrayMaxLength();
129: if (arrayMax < 0) {
130: env.getMessager().printError(
131: method.getPosition(),
132: getResourceString("jdbccontrol.bad.arraymaxlength",
133: method.getSimpleName(), arrayMax));
134: return;
135: }
136:
137: //
138: //
139: // parse the SQL
140: //
141: //
142: SqlParser _p = new SqlParser();
143: SqlStatement _statement;
144: try {
145: _statement = _p.parse(methodSQL.statement());
146: } catch (ControlException ce) {
147: env.getMessager().printError(
148: method.getPosition(),
149: getResourceString("jdbccontrol.bad.parse", method
150: .getSimpleName(), ce.toString()));
151: return;
152: }
153:
154: //
155: // Check that the any statement element params (delimited by '{' and '}' can be
156: // matched to method parameter names. NOTE: This check is only valid on non-compiled files,
157: // once compiled to a class file method parameter names are replaced with 'arg0', 'arg1', etc.
158: // and cannot be used for this check.
159: //
160: try {
161: ParameterChecker.checkReflectionParameters(_statement,
162: method);
163: } catch (ControlException e) {
164: env.getMessager().printError(method.getPosition(),
165: e.getMessage());
166: return;
167: }
168:
169: //
170: // check for case of generatedKeyColumns being set, when getGeneratedKeys is not set to true
171: //
172: final boolean getGeneratedKeys = methodSQL.getGeneratedKeys();
173: final String[] generatedKeyColumnNames = methodSQL
174: .generatedKeyColumnNames();
175: final int[] generatedKeyIndexes = methodSQL
176: .generatedKeyColumnIndexes();
177: if (!getGeneratedKeys
178: && (generatedKeyColumnNames.length != 0 || generatedKeyIndexes.length != 0)) {
179: env.getMessager().printError(
180: method.getPosition(),
181: getResourceString("jdbccontrol.genkeys", method
182: .getSimpleName()));
183: return;
184: }
185:
186: //
187: // check that both generatedKeyColumnNames and generatedKeyColumnIndexes are not set
188: //
189: if (generatedKeyColumnNames.length > 0
190: && generatedKeyIndexes.length > 0) {
191: env.getMessager().printError(
192: method.getPosition(),
193: getResourceString("jdbccontrol.genkeycolumns",
194: method.getSimpleName()));
195:
196: return;
197: }
198:
199: //
200: // batch update methods must return int[]
201: //
202: final boolean batchUpdate = methodSQL.batchUpdate();
203: final TypeMirror returnType = method.getReturnType();
204: if (batchUpdate) {
205: if (returnType instanceof ArrayType) {
206: final TypeMirror aType = ((ArrayType) returnType)
207: .getComponentType();
208: if (aType instanceof PrimitiveType == false
209: || ((PrimitiveType) aType).getKind() != PrimitiveType.Kind.INT) {
210: env.getMessager().printError(
211: method.getPosition(),
212: getResourceString(
213: "jdbccontrol.batchupdate", method
214: .getSimpleName()));
215: return;
216: }
217: } else if (returnType instanceof VoidType == false) {
218: env.getMessager().printError(
219: method.getPosition(),
220: getResourceString("jdbccontrol.batchupdate",
221: method.getSimpleName()));
222: return;
223: }
224:
225: }
226:
227: //
228: // iterator type check match
229: //
230: if (returnType instanceof InterfaceType) {
231: String iName = ((InterfaceType) returnType)
232: .getDeclaration().getQualifiedName();
233: if ("java.util.Iterator".equals(iName)) {
234: String iteratorClassName = null;
235: try {
236: // this should always except
237: methodSQL.iteratorElementType();
238: } catch (MirroredTypeException mte) {
239: iteratorClassName = mte.getQualifiedName();
240: }
241:
242: if ("org.apache.beehive.controls.system.jdbc.JdbcControl.UndefinedIteratorType"
243: .equals(iteratorClassName)) {
244: env.getMessager().printError(
245: method.getPosition(),
246: getResourceString(
247: "jdbccontrol.iterator.returntype",
248: method.getSimpleName()));
249: return;
250: }
251: }
252: }
253:
254: //
255: // scrollable result set check
256: //
257: final JdbcControl.ScrollType scrollable = methodSQL
258: .scrollableResultSet();
259: switch (scrollable) {
260: case SCROLL_INSENSITIVE:
261: case SCROLL_SENSITIVE:
262: case SCROLL_INSENSITIVE_UPDATABLE:
263: case SCROLL_SENSITIVE_UPDATABLE:
264: case FORWARD_ONLY_UPDATABLE:
265: String typeName = null;
266: if (returnType instanceof DeclaredType) {
267: typeName = ((DeclaredType) returnType).getDeclaration()
268: .getQualifiedName();
269: }
270:
271: if (typeName == null
272: || !"java.sql.ResultSet".equals(typeName)) {
273: env.getMessager().printError(
274: method.getPosition(),
275: getResourceString(
276: "jdbccontrol.scrollresultset", method
277: .getSimpleName()));
278: return;
279: }
280: case FORWARD_ONLY:
281: default:
282: break;
283: }
284:
285: return;
286: } // checkSQL
287:
288: private String getResourceString(String id, Object... args) {
289: ResourceBundle rb = ResourceBundle.getBundle(this .getClass()
290: .getPackage().getName()
291: + ".strings", _locale);
292: String pattern = rb.getString(id);
293: return MessageFormat.format(pattern, args);
294: }
295: }
|