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: package org.apache.beehive.netui.compiler.genmodel;
020:
021: import org.apache.beehive.netui.compiler.CompilerUtils;
022: import org.apache.beehive.netui.compiler.FlowControllerInfo;
023: import org.apache.beehive.netui.compiler.JpfLanguageConstants;
024: import org.apache.beehive.netui.compiler.MergedControllerAnnotation;
025: import org.apache.beehive.netui.compiler.Diagnostics;
026: import org.apache.beehive.netui.compiler.FatalCompileTimeException;
027: import org.apache.beehive.netui.compiler.model.FormBeanModel;
028: import org.apache.beehive.netui.compiler.model.ForwardModel;
029: import org.apache.beehive.netui.compiler.model.MessageResourcesModel;
030: import org.apache.beehive.netui.compiler.model.StrutsApp;
031: import org.apache.beehive.netui.compiler.model.XmlModelWriterException;
032: import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
033: import org.apache.beehive.netui.compiler.typesystem.declaration.ClassDeclaration;
034: import org.apache.beehive.netui.compiler.typesystem.declaration.MethodDeclaration;
035: import org.apache.beehive.netui.compiler.typesystem.declaration.Modifier;
036: import org.apache.beehive.netui.compiler.typesystem.declaration.TypeDeclaration;
037: import org.apache.beehive.netui.compiler.typesystem.env.CoreAnnotationProcessorEnv;
038:
039: import java.io.File;
040: import java.io.FileNotFoundException;
041: import java.io.FileOutputStream;
042: import java.io.IOException;
043: import java.io.PrintWriter;
044: import java.util.ArrayList;
045: import java.util.Collection;
046: import java.util.Date;
047: import java.util.Iterator;
048: import java.util.List;
049:
050: public class GenStrutsApp extends StrutsApp implements
051: JpfLanguageConstants {
052: private ClassDeclaration _jclass;
053: private String _containingPackage;
054: private File _strutsConfigFile;
055: private File _sourceFile;
056: private CoreAnnotationProcessorEnv _env;
057: private FlowControllerInfo _fcInfo;
058:
059: protected void recalculateStrutsConfigFile() throws IOException,
060: FatalCompileTimeException {
061: _strutsConfigFile = calculateStrutsConfigFile(); // caching this
062: }
063:
064: FlowControllerInfo getFlowControllerInfo() {
065: return _fcInfo;
066: }
067:
068: public GenStrutsApp(File sourceFile, ClassDeclaration jclass,
069: CoreAnnotationProcessorEnv env, FlowControllerInfo fcInfo,
070: boolean checkOnly, Diagnostics diagnostics)
071: throws IOException, FatalCompileTimeException {
072: super (jclass.getQualifiedName());
073:
074: _jclass = jclass;
075: _containingPackage = jclass.getPackage().getQualifiedName();
076: _sourceFile = sourceFile;
077: _env = env;
078: assert fcInfo != null;
079: _fcInfo = fcInfo;
080:
081: recalculateStrutsConfigFile();
082:
083: if (checkOnly)
084: return;
085:
086: if (_jclass != null) {
087: MergedControllerAnnotation mca = fcInfo
088: .getMergedControllerAnnotation();
089: setNestedPageFlow(mca.isNested());
090: setLongLivedPageFlow(mca.isLongLived());
091: addMessageResources(mca.getMessageResources()); // messageResources is deprecated
092: addMessageBundles(mca.getMessageBundles()); // messageBundles is not
093: addSimpleActions(mca.getSimpleActions());
094: setMultipartHandler(mca.getMultipartHandler());
095: GenForwardModel.addForwards(mca.getForwards(), this ,
096: _jclass, this , null);
097:
098: // TODO: comment
099: addForward(new ForwardModel("_auto", "", this ));
100:
101: GenExceptionModel.addCatches(mca.getCatches(), this ,
102: _jclass, this , this );
103: addTilesDefinitionsConfigs(mca.getTilesDefinitionsConfigs());
104: setAdditionalValidatorConfigs(mca
105: .getCustomValidatorConfigs());
106: addActionMethods();
107: addFormBeans(_jclass);
108: setAbstract(_jclass.hasModifier(Modifier.ABSTRACT));
109: }
110:
111: if (fcInfo != null) {
112: setSharedFlows(fcInfo.getSharedFlowTypeNames());
113: setReturnToActionDisabled(!fcInfo
114: .isNavigateToActionEnabled());
115: setReturnToPageDisabled(!fcInfo.isNavigateToPageEnabled());
116: }
117: }
118:
119: private void addFormBeans(ClassDeclaration jclass) {
120: Collection innerTypes = CompilerUtils
121: .getClassNestedTypes(jclass);
122:
123: for (Iterator ii = innerTypes.iterator(); ii.hasNext();) {
124: TypeDeclaration innerType = (TypeDeclaration) ii.next();
125: if (innerType instanceof ClassDeclaration) {
126: ClassDeclaration innerClass = (ClassDeclaration) innerType;
127:
128: if (innerType.hasModifier(Modifier.PUBLIC)
129: && CompilerUtils.isAssignableFrom(
130: PAGEFLOW_FORM_CLASS_NAME, innerClass,
131: _env)) {
132: getMatchingFormBeans(innerClass, Boolean
133: .valueOf(false));
134: }
135: }
136: }
137:
138: }
139:
140: /**
141: * Returns a non-empty List of FormBeanModels that match the given form
142: * bean type. The <code>usesPageFlowScopedFormBean</code> parameter can
143: * be used to get the FormBeanModel for either a page flow scoped bean
144: * (<code>true</code>), not flow scoped (<code>false</code>), or both
145: * (<code>null</code>).
146: * @param formType the form bean class type to match
147: * @param usesPageFlowScopedFormBean flag to indicate that the bean is
148: * page flow scoped. If null, return all FormBeanModels of the given type
149: * regardless of being flow scoped or not.
150: * @return a non-empty List of FormBeanModels that match the given type
151: */
152: List getMatchingFormBeans(TypeDeclaration formType,
153: Boolean usesPageFlowScopedFormBean) {
154: //
155: // Use the actual type of form to create the name.
156: // This avoids conflicts if there are multiple forms using the
157: // ANY_FORM_CLASS_NAME type.
158: //
159: String actualType = CompilerUtils.getLoadableName(formType);
160:
161: //
162: // See if the app already has a form-bean of this type. If so,
163: // we'll just use it; otherwise, we need to create it.
164: //
165: List formBeans = getFormBeansByActualType(actualType,
166: usesPageFlowScopedFormBean);
167:
168: if (formBeans == null) {
169: // if not indicated assume not flow scoped when adding a new bean
170: boolean isFlowScoped = false;
171: if (usesPageFlowScopedFormBean != null) {
172: isFlowScoped = usesPageFlowScopedFormBean
173: .booleanValue();
174: }
175: FormBeanModel formBeanModel = addNewFormBean(formType,
176: isFlowScoped);
177: formBeans = new ArrayList();
178: formBeans.add(formBeanModel);
179: }
180:
181: assert formBeans.size() > 0;
182: return formBeans;
183: }
184:
185: private FormBeanModel addNewFormBean(TypeDeclaration formType,
186: boolean usesFlowScopedFormBean) {
187: String actualTypeName = CompilerUtils.getLoadableName(formType);
188: String formClass = CompilerUtils.getFormClassName(formType,
189: _env);
190: String name = getFormNameForType(actualTypeName,
191: usesFlowScopedFormBean);
192: String key = getMessageResourcesFromFormType(formType);
193: FormBeanModel fb = new FormBeanModel(name, formClass,
194: actualTypeName, usesFlowScopedFormBean, key, this );
195: addFormBean(fb);
196: return fb;
197: }
198:
199: private void addMessageResources(Collection messageResources) {
200: if (messageResources != null) {
201: for (Iterator ii = messageResources.iterator(); ii
202: .hasNext();) {
203: AnnotationInstance ann = (AnnotationInstance) ii.next();
204: addMessageResources(new GenMessageBundleModel(this , ann));
205: }
206: }
207: }
208:
209: private void addMessageBundles(Collection messageBundles) {
210: if (messageBundles != null) {
211: for (Iterator ii = messageBundles.iterator(); ii.hasNext();) {
212: AnnotationInstance ann = (AnnotationInstance) ii.next();
213: addMessageResources(new GenMessageBundleModel(this , ann));
214: }
215: }
216: }
217:
218: private void addSimpleActions(Collection simpleActionAnnotations) {
219: if (simpleActionAnnotations != null) {
220: for (Iterator ii = simpleActionAnnotations.iterator(); ii
221: .hasNext();) {
222: AnnotationInstance ann = (AnnotationInstance) ii.next();
223: TypeDeclaration containingType = ann
224: .getContainingType();
225:
226: // If this is an inherited method, add a delegating action mapping.
227: if (CompilerUtils
228: .typesAreEqual(_jclass, containingType)) {
229: addActionMapping(new GenSimpleActionModel(ann,
230: this , _jclass));
231: } else {
232: addActionMapping(new DelegatingSimpleActionModel(
233: ann, containingType, this , _jclass));
234: }
235: }
236: }
237: }
238:
239: private void setMultipartHandler(String mpHandler) {
240: if (mpHandler != null) {
241: if (mpHandler.equals(MULTIPART_HANDLER_DISABLED_STR)) {
242: setMultipartHandlerClassName("none");
243: } else {
244: setMultipartHandlerClassName(COMMONS_MULTIPART_HANDLER_CLASSNAME);
245:
246: if (mpHandler.equals(MULTIPART_HANDLER_DISK_STR)) {
247: setMemFileSize("0K");
248: } else {
249: assert mpHandler
250: .equals(MULTIPART_HANDLER_MEMORY_STR) : mpHandler;
251: }
252: }
253: }
254: }
255:
256: private void addTilesDefinitionsConfigs(List tilesDefinitionsConfigs) {
257: if (tilesDefinitionsConfigs == null
258: || tilesDefinitionsConfigs.isEmpty()) {
259: return;
260: }
261:
262: List paths = new ArrayList();
263:
264: for (Iterator ii = tilesDefinitionsConfigs.iterator(); ii
265: .hasNext();) {
266: String definitionsConfig = (String) ii.next();
267:
268: if (definitionsConfig != null
269: && definitionsConfig.length() > 0) {
270: paths.add(definitionsConfig);
271: }
272: }
273:
274: setTilesDefinitionsConfigs(paths);
275: }
276:
277: private void addActionMethods() {
278: MethodDeclaration[] actionMethods = CompilerUtils
279: .getClassMethods(_jclass, ACTION_TAG_NAME);
280:
281: for (int i = 0; i < actionMethods.length; i++) {
282: MethodDeclaration actionMethod = actionMethods[i];
283:
284: if (!actionMethod.hasModifier(Modifier.ABSTRACT)) {
285: // If this is an inherited method, add a delegating action mapping.
286: TypeDeclaration declaringType = actionMethod
287: .getDeclaringType();
288: if (CompilerUtils.typesAreEqual(_jclass, declaringType)) {
289: addActionMapping(new GenActionModel(actionMethod,
290: this , _jclass));
291: } else {
292: addActionMapping(new DelegatingActionModel(
293: actionMethod, declaringType, this , _jclass));
294: }
295: }
296: }
297: }
298:
299: /**
300: * @return the message-resources key for the form bean's message bundle
301: */
302: String getMessageResourcesFromFormType(TypeDeclaration formTypeDecl) {
303: if (!(formTypeDecl instanceof ClassDeclaration))
304: return null;
305:
306: ClassDeclaration formClassDecl = (ClassDeclaration) formTypeDecl;
307:
308: AnnotationInstance ann = CompilerUtils.getAnnotation(
309: formClassDecl, FORM_BEAN_TAG_NAME, true);
310:
311: if (ann != null) {
312: String defaultMessageResources = CompilerUtils.getString(
313: ann, MESSAGE_BUNDLE_ATTR, true);
314:
315: if (defaultMessageResources != null) {
316: String key = "formMessages:"
317: + CompilerUtils.getLoadableName(formClassDecl);
318:
319: for (Iterator ii = getMessageResourcesList().iterator(); ii
320: .hasNext();) {
321: MessageResourcesModel i = (MessageResourcesModel) ii
322: .next();
323: if (key.equals(i.getKey())
324: && i.getParameter().equals(
325: defaultMessageResources))
326: return key;
327: }
328:
329: MessageResourcesModel mrm = new MessageResourcesModel(
330: this );
331: mrm.setKey(key);
332: mrm.setParameter(defaultMessageResources);
333: mrm.setReturnNull(true);
334: addMessageResources(mrm);
335: return key;
336: }
337: }
338:
339: return null;
340: }
341:
342: protected String getMergeFileName() {
343: return getFlowControllerInfo().getMergedControllerAnnotation()
344: .getStrutsMerge();
345: }
346:
347: public void writeToFile() throws FileNotFoundException,
348: IOException, XmlModelWriterException,
349: FatalCompileTimeException {
350: File strutsMergeFile = getMergeFile(getMergeFileName());
351: PrintWriter writer = getEnv().getFiler().createTextFile(
352: _strutsConfigFile);
353:
354: try {
355: writeXml(writer, strutsMergeFile);
356: } finally {
357: writer.close();
358: }
359: }
360:
361: public boolean isStale() throws FatalCompileTimeException {
362: return isStale(getMergeFile(getMergeFileName()));
363: }
364:
365: String getOutputFileURI(String filePrefix) {
366: return getOutputFileURI(filePrefix, _containingPackage, false);
367: }
368:
369: String getStrutsConfigURI() {
370: return getStrutsConfigURI(_containingPackage, false);
371: }
372:
373: protected String getContainingPackage() {
374: return _containingPackage;
375: }
376:
377: private File calculateStrutsConfigFile() {
378: return new File(getStrutsConfigURI());
379: }
380:
381: /**
382: * Tell whether the struts output file (struts-config-*.xml) is out of date, based on the
383: * file times of the source file and the (optional) struts-merge file.
384: */
385: public boolean isStale(File mergeFile) {
386: //
387: // We can write to the file if it doesn't exist yet.
388: //
389: if (!_strutsConfigFile.exists()) {
390: return true;
391: }
392:
393: long lastWrite = _strutsConfigFile.lastModified();
394:
395: if (mergeFile != null && mergeFile.exists()
396: && mergeFile.lastModified() > lastWrite) {
397: return true;
398: }
399:
400: if (_sourceFile.lastModified() > lastWrite) {
401: return true;
402: }
403:
404: return false;
405: }
406:
407: /**
408: * In some cases, canWrite() does not guarantee that a FileNotFoundException will not
409: * be thrown when trying to write to a file. This method actually tries to overwrite
410: * the file as a test to see whether it's possible.
411: */
412: public boolean canWrite() {
413: if (!_strutsConfigFile.canWrite()) {
414: return false;
415: }
416:
417: try {
418: //
419: // This appears to be the only way to predict whether the file can actually be
420: // written to; it may be that canWrite() returns true, but the file permissions
421: // (NTFS only?) will cause an exception to be thrown.
422: //
423: new FileOutputStream(_strutsConfigFile, true).close();
424: } catch (FileNotFoundException e) {
425: return false;
426: } catch (IOException e) {
427: return false;
428: }
429:
430: return true;
431: }
432:
433: public File getStrutsConfigFile() {
434: return _strutsConfigFile;
435: }
436:
437: public File getMergeFile(String mergeFileName)
438: throws FatalCompileTimeException {
439: if (mergeFileName != null) {
440: return CompilerUtils.getFileRelativeToSourceFile(_jclass,
441: mergeFileName, getEnv());
442: }
443:
444: return null;
445: }
446:
447: protected String getHeaderComment(File mergeFile)
448: throws FatalCompileTimeException {
449: StringBuffer comment = new StringBuffer(" Generated from ");
450: comment.append(getWebappRelativePath(_sourceFile));
451: if (mergeFile != null) {
452: comment.append(" and ").append(
453: getWebappRelativePath(mergeFile));
454: }
455: comment.append(" on ").append(new Date().toString())
456: .append(' ');
457: return comment.toString();
458: }
459:
460: private String getWebappRelativePath(File file)
461: throws FatalCompileTimeException {
462: String filePath = file.getAbsoluteFile().getPath();
463: String[] sourceRoots = CompilerUtils.getWebSourceRoots(_env);
464:
465: //
466: // Look through the source roots.
467: //
468: for (int i = 0; i < sourceRoots.length; i++) {
469: String sourceRoot = sourceRoots[i].replace('/',
470: File.separatorChar);
471:
472: if (filePath.startsWith(sourceRoot)) {
473: return filePath.substring(sourceRoot.length()).replace(
474: '\\', '/');
475: }
476: }
477:
478: //
479: // Look in the web content root.
480: //
481: String[] webContentRoots = CompilerUtils
482: .getWebContentRoots(getEnv());
483: for (int i = 0; i < webContentRoots.length; i++) {
484: String webContentRoot = webContentRoots[i].replace('/',
485: File.separatorChar);
486:
487: if (filePath.startsWith(webContentRoot)) {
488: return filePath.substring(webContentRoot.length())
489: .replace('\\', '/');
490: }
491: }
492:
493: return file.toString();
494: }
495:
496: CoreAnnotationProcessorEnv getEnv() {
497: return _env;
498: }
499:
500: protected String getValidationFilePrefix() {
501: return "pageflow-validation";
502: }
503:
504: ClassDeclaration getFlowControllerClass() {
505: return _jclass;
506: }
507: }
|