/* * 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.occ.roo.addon.addon; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; import org.gvnix.occ.roo.addon.annotations.GvNIXEntityOCCChecksum; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.springframework.roo.addon.finder.FinderOperationsImpl; import org.springframework.roo.addon.jpa.addon.activerecord.JpaActiveRecordMetadata; import org.springframework.roo.classpath.PhysicalTypeCategory; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.TypeManagementService; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.MemberFindingUtils; import org.springframework.roo.classpath.itd.ItdTriggerBasedMetadataProvider; import org.springframework.roo.classpath.persistence.PersistenceMemberLocator; import org.springframework.roo.classpath.scanner.MemberDetailsScanner; import org.springframework.roo.metadata.MetadataDependencyRegistry; import org.springframework.roo.metadata.MetadataIdentificationUtils; import org.springframework.roo.metadata.MetadataItem; import org.springframework.roo.metadata.MetadataNotificationListener; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.JavaType; import org.springframework.roo.process.manager.FileManager; import org.springframework.roo.process.manager.MutableFile; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.project.Path; import org.springframework.roo.support.logging.HandlerUtils; /** * gvNIX OCCChecksum Metadata provider * * @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> */ @Component @Service public class OCCChecksumMetadataProvider implements ItdTriggerBasedMetadataProvider, MetadataNotificationListener { // ------------ OSGi component attributes ---------------- private BundleContext context; private static final Logger LOGGER = HandlerUtils .getLogger(FinderOperationsImpl.class); // From AbstractItdMetadataProvider private boolean governorTypeDetail = true; private boolean governorBeingAClass = true; private MetadataService metadataService; private MetadataDependencyRegistry metadataDependencyRegistry; private FileManager fileManager; private MemberDetailsScanner memberDetailsScanner; private PersistenceMemberLocator persistenceMemberLocator; private TypeManagementService typeManagementService; /** * The annotations which, if present on a class or interface, will cause * metadata to be created */ private final List<JavaType> metadataTriggers = new ArrayList<JavaType>(); /** We don't care about trigger annotations; we always produce metadata */ private boolean ignoreTriggerAnnotations = false; protected void activate(ComponentContext cContext) { context = cContext.getBundleContext(); // Ensure we're notified of all metadata related to physical Java types, // in particular their initial creation getMetadataDependencyRegistry().registerDependency( PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType()); addMetadataTrigger(new JavaType(GvNIXEntityOCCChecksum.class.getName())); } protected void deactivate(ComponentContext context) { // TODO: } /* * (non-Javadoc) * * @see org.springframework.roo.classpath.itd.AbstractItdMetadataProvider# * createLocalIdentifier(org.springframework.roo.model.JavaType, * org.springframework.roo.project.Path) */ protected String createLocalIdentifier(JavaType javaType, LogicalPath path) { return OCCChecksumMetadata.createIdentifier(javaType, path); } /* * (non-Javadoc) * * @see org.springframework.roo.classpath.itd.ItdMetadataProvider# * getItdUniquenessFilenameSuffix() */ public String getItdUniquenessFilenameSuffix() { return "gvNIX_occChecksum"; } /* * (non-Javadoc) * * @see org.springframework.roo.metadata.MetadataProvider#getProvidesType() */ public String getProvidesType() { return OCCChecksumMetadata.getMetadataIdentiferType(); } /* * (non-Javadoc) * * @seeorg.springframework.roo.classpath.itd.AbstractItdMetadataProvider# * getGovernorPhysicalTypeIdentifier(java.lang.String) */ protected String getGovernorPhysicalTypeIdentifier( String metadataIdentificationString) { JavaType javaType = OCCChecksumMetadata .getJavaType(metadataIdentificationString); LogicalPath path = OCCChecksumMetadata .getPath(metadataIdentificationString); String physicalTypeIdentifier = PhysicalTypeIdentifier .createIdentifier(javaType, path); return physicalTypeIdentifier; } protected OCCChecksumMetadata getMetadata( String metadataIdentificationString, JavaType aspectName, PhysicalTypeMetadata governorPhysicalTypeMetadata, String itdFilename) { Path path = Path.SRC_MAIN_JAVA; // We know governor type details are non-null and can be safely cast // We get govenor's EntityMetadata JavaType entityType = governorPhysicalTypeMetadata .getMemberHoldingTypeDetails().getName(); String entityMetadataKey = JpaActiveRecordMetadata.createIdentifier( entityType, LogicalPath.getInstance(path, "")); // We get governor's Entity JpaActiveRecordMetadata entityMetadata = (JpaActiveRecordMetadata) getMetadataService() .get(entityMetadataKey); if (entityMetadata == null) { return null; } FieldMetadata versionField = getPersistenceMemberLocator() .getVersionField(entityType); if (versionField != null) { String declaredByType = entityMetadataKey .substring(entityMetadataKey.lastIndexOf('?') + 1); if (!versionField.getDeclaredByMetadataId() .endsWith(declaredByType)) { throw new IllegalStateException( "You are trying to apply OCC Checksum on an Entity " .concat("that extends of another one with a ") .concat("@javax.persistence.Version field. ") .concat("You should to apply OCC Checksum ") .concat("over that Entity in your class: ") .concat(declaredByType)); } } List<FieldMetadata> idFields = getPersistenceMemberLocator() .getIdentifierFields(entityType); if (idFields.isEmpty()) { // Can't create metadata yet return null; } FieldMetadata idField = idFields.get(0); OCCChecksumMetadata metadata = new OCCChecksumMetadata( metadataIdentificationString, aspectName, governorPhysicalTypeMetadata, entityMetadata, getMemberDetailsScanner(), getTypeManagementService(), getPersistenceMemberLocator(), idField, versionField); return metadata; } // From AbstractItdMetadataProvider public final void notify(String upstreamDependency, String downstreamDependency) { if (MetadataIdentificationUtils .isIdentifyingClass(downstreamDependency)) { Validate.isTrue( MetadataIdentificationUtils.getMetadataClass( upstreamDependency).equals( MetadataIdentificationUtils .getMetadataClass(PhysicalTypeIdentifier .getMetadataIdentiferType())), "Expected class-level notifications only for physical Java types (not '" + upstreamDependency + "')"); // A physical Java type has changed, and determine what the // corresponding local metadata identification string would have // been JavaType javaType = PhysicalTypeIdentifier .getJavaType(upstreamDependency); LogicalPath path = PhysicalTypeIdentifier .getPath(upstreamDependency); downstreamDependency = createLocalIdentifier(javaType, path); // We only need to proceed if the downstream dependency relationship // is not already registered // (if it's already registered, the event will be delivered directly // later on) if (getMetadataDependencyRegistry().getDownstream( upstreamDependency).contains(downstreamDependency)) { return; } } // We should now have an instance-specific "downstream dependency" that // can be processed by this class Validate.isTrue( MetadataIdentificationUtils.getMetadataClass( downstreamDependency).equals( MetadataIdentificationUtils .getMetadataClass(getProvidesType())), "Unexpected downstream notification for '" + downstreamDependency + "' to this provider (which uses '" + getProvidesType() + "'"); getMetadataService().evict(downstreamDependency); if (get(downstreamDependency) != null) { getMetadataDependencyRegistry().notifyDownstream( downstreamDependency); } } /** * Registers an additional {@link JavaType} that will trigger metadata * registration. * * @param javaType the type-level annotation to detect that will cause * metadata creation (required) */ public void addMetadataTrigger(JavaType javaType) { Validate.notNull(javaType, "Java type required for metadata trigger registration"); this.metadataTriggers.add(javaType); } /** * Removes a {@link JavaType} metadata trigger registration. If the type was * never registered, the method returns without an error. * * @param javaType to remove (required) */ public void removeMetadataTrigger(JavaType javaType) { Validate.notNull(javaType, "Java type required for metadata trigger deregistration"); this.metadataTriggers.remove(javaType); } protected boolean isIgnoreTriggerAnnotations() { return ignoreTriggerAnnotations; } protected void setIgnoreTriggerAnnotations(boolean ignoreTriggerAnnotations) { this.ignoreTriggerAnnotations = ignoreTriggerAnnotations; } /* * (non-Javadoc) * * TODO Overwritted method required because in old Roo versions can't use * builder to include AJs precedence. Used a template instead. * * @see * org.springframework.roo.metadata.MetadataProvider#get(java.lang.String) */ public final MetadataItem get(String metadataIdentificationString) { Validate.isTrue( MetadataIdentificationUtils.getMetadataClass( metadataIdentificationString).equals( MetadataIdentificationUtils .getMetadataClass(getProvidesType())), "Unexpected request for '" + metadataIdentificationString + "' to this provider (which uses '" + getProvidesType() + "'"); // Remove the upstream dependencies for this instance (we'll be // recreating them later, if needed) getMetadataDependencyRegistry().deregisterDependencies( metadataIdentificationString); // Compute the identifier for the Physical Type Metadata we're // correlated with String governorPhysicalTypeIdentifier = getGovernorPhysicalTypeIdentifier(metadataIdentificationString); // Obtain the physical type PhysicalTypeMetadata governorPhysicalTypeMetadata = (PhysicalTypeMetadata) getMetadataService() .get(governorPhysicalTypeIdentifier); if (governorPhysicalTypeMetadata == null || !governorPhysicalTypeMetadata.isValid()) { // We can't get even basic information about the physical type, so // abort (the ITD will be deleted by ItdFileDeletionService) return null; } // Determine ITD details String itdFilename = governorPhysicalTypeMetadata .getItdCanoncialPath(this); JavaType aspectName = governorPhysicalTypeMetadata.getItdJavaType(this); // Flag to indicate whether we'll even try to create this metadata boolean produceMetadata = false; // Determine if we should generate the metadata on the basis of it // containing a trigger annotation ClassOrInterfaceTypeDetails cid = null; if (governorPhysicalTypeMetadata.getMemberHoldingTypeDetails() != null && governorPhysicalTypeMetadata.getMemberHoldingTypeDetails() instanceof ClassOrInterfaceTypeDetails) { cid = (ClassOrInterfaceTypeDetails) governorPhysicalTypeMetadata .getMemberHoldingTypeDetails(); // Only create metadata if the type is annotated with one of the // metadata triggers for (JavaType trigger : metadataTriggers) { if (MemberFindingUtils.getDeclaredTypeAnnotation(cid, trigger) != null) { produceMetadata = true; break; } } } // Fallback to ignoring trigger annotations if (ignoreTriggerAnnotations) { produceMetadata = true; } // Cancel production if the governor type details are required, but // aren't available if (governorTypeDetail && cid == null) { produceMetadata = false; } // Cancel production if the governor is not a class, and the subclass // only wants to know about classes if (cid != null && governorBeingAClass && cid.getPhysicalTypeCategory() != PhysicalTypeCategory.CLASS) { produceMetadata = false; } if (produceMetadata) { // This type contains an annotation we were configured to detect, or // there is an ITD (which may need deletion), so we need to produce // the metadata OCCChecksumMetadata metadata; metadata = getMetadata(metadataIdentificationString, aspectName, governorPhysicalTypeMetadata, itdFilename); // Register a direct connection between the physical type and this // metadata // (this is needed so changes to the inheritance hierarchies are // eventually notified to us) getMetadataDependencyRegistry().registerDependency( governorPhysicalTypeMetadata.getId(), metadataIdentificationString); // Quit if the subclass returned null; it might not have experienced // issues parsing etc if (metadata == null) { return null; } // Handle the management of the ITD file if (metadata.getItdFileContents() != null) { String itd = metadata.getItdFileContents(); // Output the ITD if there is actual content involved // (if there is no content, we continue on to the deletion phase // at the bottom of this conditional block) if (itd.length() > 0) { MutableFile mutableFile = null; if (getFileManager().exists(itdFilename)) { // First verify if the file has even changed File newFile = new File(itdFilename); String existing = null; try { existing = IOUtils .toString(new FileReader(newFile)); } catch (IOException ignoreAndJustOverwriteIt) { LOGGER.finest("Problem writting ".concat( newFile.getAbsolutePath()).concat(": ") + ignoreAndJustOverwriteIt); } if (!itd.equals(existing)) { mutableFile = getFileManager().updateFile( itdFilename); } } else { mutableFile = getFileManager().createFile(itdFilename); Validate.notNull(mutableFile, "Could not create ITD file '" + itdFilename + "'"); } try { if (mutableFile != null) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = IOUtils.toInputStream(itd); outputStream = mutableFile.getOutputStream(); IOUtils.copy(inputStream, outputStream); } finally { IOUtils.closeQuietly(inputStream); IOUtils.closeQuietly(outputStream); } } } catch (IOException ioe) { throw new IllegalStateException("Could not output '" .concat(mutableFile.getCanonicalPath()).concat( "'"), ioe); } // Important to exit here, so we don't proceed onto the // delete operation below // (as we have a valid ITD that has been written out by now) return metadata; } } // Delete the ITD if we determine deletion is appropriate // DISID: Removed because in shell restart some AJ files deleted // and not recreated // if (metadata.isValid() && getFileManager().exists(itdFilename)) { // getFileManager().delete(itdFilename); // } return metadata; } return null; } public final String getIdForPhysicalJavaType( String physicalJavaTypeIdentifier) { Validate.isTrue( MetadataIdentificationUtils.getMetadataClass( physicalJavaTypeIdentifier).equals( MetadataIdentificationUtils .getMetadataClass(PhysicalTypeIdentifier .getMetadataIdentiferType())), "Expected a valid physical Java type instance identifier (not '" + physicalJavaTypeIdentifier + "')"); JavaType javaType = PhysicalTypeIdentifier .getJavaType(physicalJavaTypeIdentifier); LogicalPath path = PhysicalTypeIdentifier .getPath(physicalJavaTypeIdentifier); return createLocalIdentifier(javaType, path); } /** * If set to true (default is true), ensures subclass not called unless the * governor type details are available. * * @param governorTypeDetail true means governor type details must be * available */ public void setDependsOnGovernorTypeDetailAvailability( boolean governorTypeDetail) { this.governorTypeDetail = governorTypeDetail; } /** * If set to true (default is true), ensures the governor type details * represent a class. Note that * {@link #setDependsOnGovernorTypeDetailAvailability(boolean)} must also be * true to ensure this can be relied upon. * * @param governorBeingAClass true means governor type detail must represent * a class */ public void setDependsOnGovernorBeingAClass(boolean governorBeingAClass) { this.governorBeingAClass = governorBeingAClass; } public MetadataDependencyRegistry getMetadataDependencyRegistry() { if (metadataDependencyRegistry == null) { // Get all Services implement MetadataDependencyRegistry interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( MetadataDependencyRegistry.class.getName(), null); for (ServiceReference<?> ref : references) { return (MetadataDependencyRegistry) this.context .getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load MetadataDependencyRegistry on OCCChecksumMetadataProvider."); return null; } } else { return metadataDependencyRegistry; } } public MetadataService getMetadataService() { if (metadataService == null) { // Get all Services implement MetadataService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( MetadataService.class.getName(), null); for (ServiceReference<?> ref : references) { return (MetadataService) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load MetadataService on OCCChecksumMetadataProvider."); return null; } } else { return metadataService; } } public FileManager getFileManager() { if (fileManager == null) { // Get all Services implement FileManager interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences(FileManager.class.getName(), null); for (ServiceReference<?> ref : references) { return (FileManager) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load FileManager on OCCChecksumMetadataProvider."); return null; } } else { return fileManager; } } public MemberDetailsScanner getMemberDetailsScanner() { if (memberDetailsScanner == null) { // Get all Services implement MemberDetailsScanner interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( MemberDetailsScanner.class.getName(), null); for (ServiceReference<?> ref : references) { return (MemberDetailsScanner) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load MemberDetailsScanner on OCCChecksumMetadataProvider."); return null; } } else { return memberDetailsScanner; } } public PersistenceMemberLocator getPersistenceMemberLocator() { if (persistenceMemberLocator == null) { // Get all Services implement PersistenceMemberLocator interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( PersistenceMemberLocator.class.getName(), null); for (ServiceReference<?> ref : references) { return (PersistenceMemberLocator) this.context .getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load PersistenceMemberLocator on OCCChecksumMetadataProvider."); return null; } } else { return persistenceMemberLocator; } } public TypeManagementService getTypeManagementService() { if (typeManagementService == null) { // Get all Services implement TypeManagementService interface try { ServiceReference<?>[] references = this.context .getAllServiceReferences( TypeManagementService.class.getName(), null); for (ServiceReference<?> ref : references) { return (TypeManagementService) this.context.getService(ref); } return null; } catch (InvalidSyntaxException e) { LOGGER.warning("Cannot load TypeManagementService on OCCChecksumMetadataProvider."); return null; } } else { return typeManagementService; } } }