/* * gvNIX is an open source tool for rapid application development (RAD). * Copyright (C) 2010 Generalitat Valenciana * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.gvnix.addon.datatables.addon; import java.beans.Introspector; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.logging.Logger; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; import org.gvnix.addon.datatables.annotations.GvNIXDatatables; import org.gvnix.addon.jpa.addon.query.JpaQueryMetadata; import org.gvnix.addon.web.mvc.addon.batch.WebJpaBatchMetadata; import org.gvnix.support.PhysicalTypeUtils; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.finder.DynamicFinderServicesImpl; import org.springframework.roo.addon.finder.FieldToken; import org.springframework.roo.addon.finder.FinderFieldTokenMissingException; import org.springframework.roo.addon.finder.FinderMetadata; import org.springframework.roo.addon.finder.InvalidFinderException; import org.springframework.roo.addon.finder.QueryHolder; import org.springframework.roo.addon.finder.ReservedToken; import org.springframework.roo.addon.finder.ReservedTokenHolder; import org.springframework.roo.addon.finder.Token; import org.springframework.roo.addon.jpa.addon.activerecord.JpaActiveRecordMetadata; import org.springframework.roo.addon.plural.addon.PluralMetadata; import org.springframework.roo.addon.web.mvc.controller.addon.details.DateTimeFormatDetails; import org.springframework.roo.addon.web.mvc.controller.addon.details.FinderMetadataDetails; import org.springframework.roo.addon.web.mvc.controller.addon.details.WebMetadataService; import org.springframework.roo.addon.web.mvc.controller.addon.details.WebMetadataServiceImpl; import org.springframework.roo.addon.web.mvc.controller.addon.finder.WebFinderMetadata; import org.springframework.roo.addon.web.mvc.controller.addon.scaffold.WebScaffoldAnnotationValues; import org.springframework.roo.addon.web.mvc.controller.addon.scaffold.WebScaffoldMetadata; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.BeanInfoUtils; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.MemberHoldingTypeDetails; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.itd.AbstractItdMetadataProvider; import org.springframework.roo.classpath.itd.ItdTypeDetailsProvidingMetadataItem; import org.springframework.roo.classpath.scanner.MemberDetails; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.ProjectOperations; import org.springframework.roo.support.logging.HandlerUtils; /** * Provides {@link DatatablesMetadata}. * * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a * href="http://www.dgti.gva.es">General Directorate for Information * Technologies (DGTI)</a> * @since 1.1 */ @Component @Service public final class DatatablesMetadataProvider extends AbstractItdMetadataProvider { private static final Logger LOGGER = HandlerUtils .getLogger(DatatablesMetadataProvider.class); private WebMetadataService webMetadataService; protected ProjectOperations projectOperations; private PhysicalTypeUtils physicalTypeUtils; /** * Register itself into metadataDependencyRegister and add metadata trigger * * @param context the component context */ protected void activate(ComponentContext cContext) { context = cContext.getBundleContext(); getMetadataDependencyRegistry().registerDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); addMetadataTrigger(new JavaType(GvNIXDatatables.class.getName())); } /** * Unregister this provider * * @param context the component context */ protected void deactivate(ComponentContext context) { getMetadataDependencyRegistry().deregisterDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); removeMetadataTrigger(new JavaType(GvNIXDatatables.class.getName())); } /** * Return an instance of the Metadata offered by this add-on */ protected ItdTypeDetailsProvidingMetadataItem getMetadata( String metadataIdentificationString, JavaType aspectName, PhysicalTypeMetadata governorPhysicalTypeMetadata, String itdFilename) { JavaType javaType = DatatablesMetadata .getJavaType(metadataIdentificationString); LogicalPath path = DatatablesMetadata .getPath(metadataIdentificationString); final DatatablesAnnotationValues annotationValues = new DatatablesAnnotationValues( governorPhysicalTypeMetadata); // Get webScaffoldMetadata String webScaffoldMetadataId = WebScaffoldMetadata.createIdentifier( javaType, path); WebScaffoldMetadata webScaffoldMetadata = (WebScaffoldMetadata) getMetadataService() .get(webScaffoldMetadataId); // register dependency to Roo Web Scaffold getMetadataDependencyRegistry().registerDependency( webScaffoldMetadataId, metadataIdentificationString); if (webScaffoldMetadata == null) { return null; } JavaType webScaffoldAspectName = webScaffoldMetadata.getAspectName(); WebScaffoldAnnotationValues webScaffoldAnnotationValues = webScaffoldMetadata .getAnnotationValues(); // Get formBackingObject and its logical path JavaType entity = webScaffoldAnnotationValues.getFormBackingObject(); LogicalPath entityPath = getPhysicalTypeUtils().getPath(entity, getTypeLocationService()); // Get batch service (if any) String webJpaBatchMetadataId = WebJpaBatchMetadata.createIdentifier( javaType, path); WebJpaBatchMetadata webJpaBatchMetadata = (WebJpaBatchMetadata) getMetadataService() .get(webJpaBatchMetadataId); // register dependency to Batch service getMetadataDependencyRegistry().registerDependency( webJpaBatchMetadataId, metadataIdentificationString); // Get jpa query metadata String jpaQueryMetadataId = JpaQueryMetadata.createIdentifier(entity, path); JpaQueryMetadata jpaQueryMetadata = (JpaQueryMetadata) getMetadataService() .get(jpaQueryMetadataId); // register dependency to JPA Query getMetadataDependencyRegistry().registerDependency(jpaQueryMetadataId, metadataIdentificationString); List<FieldMetadata> identifiers = getPersistenceMemberLocator() .getIdentifierFields(entity); if (identifiers.isEmpty()) { // Unsupported type (by now) return null; } // Get Plural final ClassOrInterfaceTypeDetails classDetails = getTypeLocationService() .getTypeDetails(entity); final LogicalPath pathEntity = PhysicalTypeIdentifier .getPath(classDetails.getDeclaredByMetadataId()); final String pluralId = PluralMetadata.createIdentifier(entity, pathEntity); final PluralMetadata pluralMetadata = (PluralMetadata) getMetadataService() .get(pluralId); String plural = pluralMetadata.getPlural(); // check if has metadata types final MemberDetails entityMemberDetails = getMemberDetails(entity); final Map<JavaSymbolName, DateTimeFormatDetails> datePatterns = getWebMetadataService() .getDatePatterns(entity, entityMemberDetails, metadataIdentificationString); // Identify if controller is annotated with @RooWebFinders Map<FinderMetadataDetails, QueryHolderTokens> findersRegistered = null; String webFinderMetadataId = WebFinderMetadata.createIdentifier( javaType, path); WebFinderMetadata webFinderMetadata = (WebFinderMetadata) getMetadataService() .get(webFinderMetadataId); // register dependency to Roo Web finder getMetadataDependencyRegistry().registerDependency(webFinderMetadataId, metadataIdentificationString); if (webFinderMetadata != null) { // Locate finders details findersRegistered = getFindersRegisterd(entity, path, entityMemberDetails, plural, entity.getSimpleTypeName()); } return new DatatablesMetadata(metadataIdentificationString, aspectName, governorPhysicalTypeMetadata, annotationValues, entity, entityMemberDetails, identifiers, plural, datePatterns, webScaffoldAspectName, webJpaBatchMetadata, jpaQueryMetadata, webScaffoldAnnotationValues, findersRegistered, getWebMetadataService(), getProjectOperations()); } /** * Locates All {@link FinderMetadataDetails} and its related * {@link QueryHolder} for every declared dynamic finder <br> * <br> * <em>Note:</em> This method is similar to * {@link WebMetadataServiceImpl#getDynamicFinderMethodsAndFields(JavaType, MemberDetails, String)} * but without register dependency (this dependency produces NPE in * {@link #getMetadata(String, JavaType, PhysicalTypeMetadata, String)} when * it tries to get JPA information) * * @param entity * @param path * @param entityMemberDetails * @param plural * @param entityName * @return * @see WebMetadataServiceImpl#getDynamicFinderMethodsAndFields(JavaType, * MemberDetails, String) */ private Map<FinderMetadataDetails, QueryHolderTokens> getFindersRegisterd( JavaType entity, LogicalPath path, MemberDetails entityMemberDetails, String plural, String entityName) { // Get finder metadata final String finderMetadataKey = FinderMetadata.createIdentifier( entity, path); final FinderMetadata finderMetadata = (FinderMetadata) getMetadataService() .get(finderMetadataKey); if (finderMetadata == null) { return null; } QueryHolderTokens queryHolder; FinderMetadataDetails details; Map<FinderMetadataDetails, QueryHolderTokens> findersRegistered = new HashMap<FinderMetadataDetails, QueryHolderTokens>(); // Iterate over for (final MethodMetadata method : finderMetadata .getAllDynamicFinders()) { final List<JavaSymbolName> parameterNames = method .getParameterNames(); final List<JavaType> parameterTypes = AnnotatedJavaType .convertFromAnnotatedJavaTypes(method.getParameterTypes()); final List<FieldMetadata> fields = new ArrayList<FieldMetadata>(); for (int i = 0; i < parameterTypes.size(); i++) { JavaSymbolName fieldName = null; if (parameterNames.get(i).getSymbolName().startsWith("max") || parameterNames.get(i).getSymbolName() .startsWith("min")) { fieldName = new JavaSymbolName( Introspector.decapitalize(StringUtils .capitalize(parameterNames.get(i) .getSymbolName().substring(3)))); } else { fieldName = parameterNames.get(i); } final FieldMetadata field = BeanInfoUtils .getFieldForPropertyName(entityMemberDetails, fieldName); if (field != null) { final FieldMetadataBuilder fieldMd = new FieldMetadataBuilder( field); fieldMd.setFieldName(parameterNames.get(i)); fields.add(fieldMd.build()); } } details = new FinderMetadataDetails(method.getMethodName() .getSymbolName(), method, fields); // locate QueryHolder instances. This objects contain // information about a roo finder (parameters names and types // and a "token" list with of find definition queryHolder = getQueryHolder(entityMemberDetails, method.getMethodName(), plural, entityName); findersRegistered.put(details, queryHolder); } return findersRegistered; } /** * Define the unique ITD file name extension, here the resulting file name * will be **_ROO_GvNIXDatatables.aj */ public String getItdUniquenessFilenameSuffix() { return "GvNIXDatatables"; } protected String getGovernorPhysicalTypeIdentifier( String metadataIdentificationString) { JavaType javaType = DatatablesMetadata .getJavaType(metadataIdentificationString); LogicalPath path = DatatablesMetadata .getPath(metadataIdentificationString); return PhysicalTypeIdentifier.createIdentifier(javaType, path); } protected String createLocalIdentifier(JavaType javaType, LogicalPath path) { return DatatablesMetadata.createIdentifier(javaType, path); } public String getProvidesType() { return DatatablesMetadata.getMetadataIdentiferType(); } /***************************************************/ /** Methods cloned from DynamicFinderServicesImpl **/ /***************************************************/ /** * @see DynamicFinderServicesImpl#getQueryHolder(MemberDetails, * JavaSymbolName, String, String) */ public QueryHolderTokens getQueryHolder(final MemberDetails memberDetails, final JavaSymbolName finderName, final String plural, final String entityName) { Validate.notNull(memberDetails, "Member details required"); Validate.notNull(finderName, "Finder name required"); Validate.notBlank(plural, "Plural required"); List<Token> tokens; try { tokens = tokenize(memberDetails, finderName, plural); } catch (final FinderFieldTokenMissingException e) { return null; } catch (final InvalidFinderException e) { return null; } // final String simpleTypeName = getConcreteJavaType(memberDetails) // .getSimpleTypeName(); // final String jpaQuery = getJpaQuery(tokens, simpleTypeName, // finderName, // plural, entityName); final List<JavaType> parameterTypes = getParameterTypes(tokens, finderName, plural); final List<JavaSymbolName> parameterNames = getParameterNames(tokens, finderName, plural); return new QueryHolderTokens("", parameterTypes, parameterNames, tokens); } /** * @see DynamicFinderServicesImpl#getConcreteJavaType */ private JavaType getConcreteJavaType(final MemberDetails memberDetails) { Validate.notNull(memberDetails, "Member details required"); JavaType javaType = null; for (final MemberHoldingTypeDetails memberHoldingTypeDetails : memberDetails .getDetails()) { if (Modifier.isAbstract(memberHoldingTypeDetails.getModifier())) { continue; } javaType = memberHoldingTypeDetails.getName(); } return javaType; } /** * @see DynamicFinderServicesImpl#tokenize */ private List<Token> tokenize(final MemberDetails memberDetails, final JavaSymbolName finderName, final String plural) { final String simpleTypeName = getConcreteJavaType(memberDetails) .getSimpleTypeName(); String finder = finderName.getSymbolName(); // Just in case it starts with findBy we can remove it here final String findBy = "find" + plural + "By"; if (finder.startsWith(findBy)) { finder = finder.substring(findBy.length()); } // If finder still contains the findBy sequence it is most likely a // wrong finder (ie someone pasted the finder string accidentally twice if (finder.contains(findBy)) { throw new InvalidFinderException("Dynamic finder definition for '" + finderName.getSymbolName() + "' in " + simpleTypeName + ".java is invalid"); } final SortedSet<FieldToken> fieldTokens = new TreeSet<FieldToken>(); for (final MethodMetadata method : getLocatedMutators(memberDetails)) { final FieldMetadata field = BeanInfoUtils.getFieldForPropertyName( memberDetails, method.getParameterNames().get(0)); // If we did find a field matching the first parameter name of the // mutator method we can add it to the finder ITD if (field != null) { fieldTokens.add(new FieldToken(field)); } } final List<Token> tokens = new ArrayList<Token>(); while (finder.length() > 0) { final Token token = getFirstToken(fieldTokens, finder, finderName.getSymbolName(), simpleTypeName); if (token != null) { if (token instanceof FieldToken || token instanceof ReservedToken) { tokens.add(token); } finder = finder.substring(token.getValue().length()); } } return tokens; } /** * @see DynamicFinderServicesImpl#getLocatedMutators */ private List<MethodMetadata> getLocatedMutators( final MemberDetails memberDetails) { final List<MethodMetadata> locatedMutators = new ArrayList<MethodMetadata>(); for (final MethodMetadata method : memberDetails.getMethods()) { if (isMethodOfInterest(method)) { locatedMutators.add(method); } } return locatedMutators; } /** * @see DynamicFinderServicesImpl#getFirstToken */ private Token getFirstToken(final SortedSet<FieldToken> fieldTokens, final String finder, final String originalFinder, final String simpleTypeName) { for (final FieldToken fieldToken : fieldTokens) { if (finder.startsWith(fieldToken.getValue())) { return fieldToken; } } for (final ReservedToken reservedToken : ReservedTokenHolder.ALL_TOKENS) { if (finder.startsWith(reservedToken.getValue())) { return reservedToken; } } if (finder.length() > 0) { // TODO: Make this a FinderFieldTokenMissingException instead, to // make it easier to detect this throw new FinderFieldTokenMissingException( "Dynamic finder is unable to match '" + finder + "' token of '" + originalFinder + "' finder definition in " + simpleTypeName + ".java"); } return null; // Finder does not start with reserved or field token } /** * @see DynamicFinderServicesImpl#isMethodOfInterest */ private boolean isMethodOfInterest(final MethodMetadata method) { return method.getMethodName().getSymbolName().startsWith("set") && method.getModifier() == Modifier.PUBLIC; } /** * @see DynamicFinderServicesImpl#getParameterTypes */ private List<JavaType> getParameterTypes(final List<Token> tokens, final JavaSymbolName finderName, final String plural) { final List<JavaType> parameterTypes = new ArrayList<JavaType>(); for (int i = 0; i < tokens.size(); i++) { final Token token = tokens.get(i); if (token instanceof FieldToken) { parameterTypes.add(((FieldToken) token).getField() .getFieldType()); } else { if ("Between".equals(token.getValue())) { final Token field = tokens.get(i - 1); if (field instanceof FieldToken) { parameterTypes.add(parameterTypes.get(parameterTypes .size() - 1)); } } else if ("IsNull".equals(token.getValue()) || "IsNotNull".equals(token.getValue())) { final Token field = tokens.get(i - 1); if (field instanceof FieldToken) { parameterTypes.remove(parameterTypes.size() - 1); } } } } return parameterTypes; } /** * @see DynamicFinderServicesImpl#getParameterNames */ private List<JavaSymbolName> getParameterNames(final List<Token> tokens, final JavaSymbolName finderName, final String plural) { final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); for (int i = 0; i < tokens.size(); i++) { final Token token = tokens.get(i); if (token instanceof FieldToken) { final String fieldName = ((FieldToken) token).getField() .getFieldName().getSymbolName(); parameterNames.add(new JavaSymbolName(fieldName)); } else { if ("Between".equals(token.getValue())) { final Token field = tokens.get(i - 1); if (field instanceof FieldToken) { final JavaSymbolName fieldName = parameterNames .get(parameterNames.size() - 1); // Remove the last field token parameterNames.remove(parameterNames.size() - 1); // Replace by a min and a max value parameterNames .add(new JavaSymbolName( "min" + fieldName .getSymbolNameCapitalisedFirstLetter())); parameterNames .add(new JavaSymbolName( "max" + fieldName .getSymbolNameCapitalisedFirstLetter())); } } else if ("IsNull".equals(token.getValue()) || "IsNotNull".equals(token.getValue())) { final Token field = tokens.get(i - 1); if (field instanceof FieldToken) { parameterNames.remove(parameterNames.size() - 1); } } } } return parameterNames; } public WebMetadataService getWebMetadataService() { if (webMetadataService == null) { // Get all Services implement WebMetadataService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( WebMetadataService.class.getName(), null); for (ServiceReference<?> ref : references) { return (WebMetadataService) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load WebMetadataService on DatatablesMetadataProvider."); return null; } } else { return webMetadataService; } } public ProjectOperations getProjectOperations() { if (projectOperations == null) { // Get all Services implement WebMetadataService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( ProjectOperations.class.getName(), null); for (ServiceReference<?> ref : references) { return (ProjectOperations) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load ProjectOperations on DatatablesMetadataProvider."); return null; } } else { return projectOperations; } } public PhysicalTypeUtils getPhysicalTypeUtils() { if (physicalTypeUtils == null) { // Get all Services implement PhysicalTypeUtils interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( PhysicalTypeUtils.class.getName(), null); for (ServiceReference<?> ref : references) { physicalTypeUtils = (PhysicalTypeUtils) this.context .getService(ref); return physicalTypeUtils; } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load PhysicalTypeUtils on DatatablesMetadataProvider."); return null; } } else { return physicalTypeUtils; } } }