package org.springframework.roo.classpath.itd;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.springframework.roo.classpath.details.ItdTypeDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataItem;
/**
* Simplifies the development of {@link ItdMetadataProvider}s that wish to
* automatically discover new {@link ItdTypeDetails} that become available
* elsewhere in the system, even if the {@link ItdMetadataProvider} is not
* registered as a downstream dependency.
* <p>
* This class helps solves the common requirement of not knowing which other
* add-ons might be providing metadata you are interested in. While
* {@link MemberDetailsScanner} will locate all metadata presently available in
* the system, this is a snapshot of metadata at that moment in time. While it's
* simple (and normal practice) to register as a downstream dependency of
* scanned metadata that you wish to monitor, this approach is insufficient to
* discover new metadata that subsequently becomes available (or even discover
* metadata that previously was available but did not contain members of
* interest at that moment in time and were therefore not registered as a
* dependency to monitor).
* <p>
* The practical solution to these problems is to subclass this class, implement
* the abstract {@link #getLocalMidToRequest(ItdTypeDetails)}, and in the
* activate method register a generic listener as described in the documentation
* for {@link #notifyForGenericListener(String)}. This class will then receive
* all generic notifications, determine if they relate to an ITD, extract the
* {@link ItdTypeDetails}, and present it to
* {@link #getLocalMidToRequest(ItdTypeDetails)}. The latter method allows the
* subclass to decide if they want to be formally asked for new metadata, in
* which case this class will do so. Importantly the subclass can decide the
* exact metadata identification string to request, allowing flexibility in
* implementation (eg a subclass could monitor for new ITD metadata related to
* types other than simply their normal governor types).
*
* @author Ben Alex
* @since 1.1.1
*/
@Component(componentAbstract = true)
public abstract class AbstractMemberDiscoveringItdMetadataProvider extends
AbstractItdMetadataProvider {
/**
* Allows a subclass to assess a recently updated {@link ItdTypeDetails} and
* decide whether they are interested in a metadata request being made in
* response. Subclasses will generally iterate over the passed ITD details
* and search for members of interest. If any members of interest are
* located, subclasses will return an instance-specific metadata
* identification string (MID) consistent with the subclass'
* {@link #getProvidesType()} The requested MID will be cleared from the
* cache and formally requested. This process allows subclasses to
* effectively discover new ITD members that appear over time without
* needing to process every request themselves.
*
* @param itdTypeDetails a valid {@link ItdTypeDetails} from which member
* information is available (never null)
* @return null if the subclass is not interested in the type, or a MID if
* it is
*/
protected abstract String getLocalMidToRequest(ItdTypeDetails itdTypeDetails);
/**
* Receives generic notification events arising from our calling of
* MetadataDependencyRegistry.addNotificationListener(this). You must still
* register in the activate method to receive these events, as described in
* the JavaDocs for the superclass method of the same name.
*
* @see AbstractItdMetadataProvider#notifyForGenericListener(String)
*/
@Override
protected final void notifyForGenericListener(final String upstreamDependency) {
if (MetadataIdentificationUtils.isIdentifyingClass(upstreamDependency)) {
// It's just a class-specific notification (i.e. no instance), so we
// don't care about it
return;
}
// We have an instance-specific identifier; try to get its metadata
final MetadataItem metadata = getMetadataService().get(upstreamDependency);
// We don't have to worry about physical type metadata, as we monitor
// the relevant .java once the DOD governor is first detected
if (!(metadata instanceof ItdTypeDetailsProvidingMetadataItem) || !metadata.isValid()) {
// It's not for an ITD, or there's something wrong with it
return;
}
// Get the details of the ITD
final ItdTypeDetails itdTypeDetails =
((ItdTypeDetailsProvidingMetadataItem) metadata).getMemberHoldingTypeDetails();
if (itdTypeDetails == null) {
return;
}
// Ask the subclass if they'd like us to request a MetadataItem in
// response
final String localMid = getLocalMidToRequest(itdTypeDetails);
if (localMid != null) {
Validate.isTrue(MetadataIdentificationUtils.isIdentifyingInstance(localMid),
"Metadata identification string '%s' should identify a specific instance to request",
localMid);
Validate
.isTrue(
MetadataIdentificationUtils.getMetadataClass(localMid).equals(
MetadataIdentificationUtils.getMetadataClass(getProvidesType())),
"Metadata identification string '%s' is incompatible with this metadata provider's class '%s'",
MetadataIdentificationUtils.getMetadataClass(localMid),
MetadataIdentificationUtils.getMetadataClass(getProvidesType()));
getMetadataService().evictAndGet(localMid);
}
}
}