package org.springframework.roo.metadata.internal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataLogger;
import org.springframework.roo.metadata.MetadataNotificationListener;
import org.springframework.roo.metadata.MetadataService;
/**
* Default implementation of {@link MetadataDependencyRegistry}.
* <p>
* This implementation is not thread safe. It should only be accessed by a
* single thread at a time. This is enforced by the process manager semantics,
* so we avoid the cost of re-synchronization here.
*
* @author Ben Alex
* @since 1.0
*/
@Component
@Service
public class DefaultMetadataDependencyRegistry implements MetadataDependencyRegistry {
/** key: downstream dependency; value: list<upstream dependencies> */
private final Map<String, Set<String>> downstreamKeyed = new HashMap<String, Set<String>>();
/** key: upstream dependency; value: list<downstream dependencies> */
private final Map<String, Set<String>> upstreamKeyed = new HashMap<String, Set<String>>();
private final Set<MetadataNotificationListener> listeners =
new HashSet<MetadataNotificationListener>();
@Reference(policy = ReferencePolicy.DYNAMIC)
protected volatile MetadataLogger metadataLogger;
protected MetadataService metadataService;
public void addNotificationListener(final MetadataNotificationListener listener) {
Validate.notNull(listener, "Metadata notification listener required");
if (listener instanceof MetadataService) {
Validate.isTrue(metadataService == null, "Cannot register more than one MetadataListener");
metadataService = (MetadataService) listener;
return;
}
listeners.add(listener);
}
private void buildSetOfAllUpstreamDependencies(final Set<String> results,
final String downstreamDependency) {
final Set<String> upstreams = downstreamKeyed.get(downstreamDependency);
if (upstreams == null) {
return;
}
for (final String upstream : upstreams) {
results.add(upstream);
buildSetOfAllUpstreamDependencies(results, upstream);
}
}
public void deregisterDependencies(final String downstreamDependency) {
Validate.isTrue(MetadataIdentificationUtils.isValid(downstreamDependency),
"Downstream dependency is an invalid metadata identification string ('%s')",
downstreamDependency);
// Acquire the keys to delete
final Set<String> upstream = downstreamKeyed.get(downstreamDependency);
if (upstream == null) {
return;
}
final Set<String> upstreamToDelete = new HashSet<String>(upstream);
// Delete them normally
for (final String deleteUpstream : upstreamToDelete) {
deregisterDependency(deleteUpstream, downstreamDependency);
}
}
public void deregisterDependency(final String upstreamDependency,
final String downstreamDependency) {
Validate.isTrue(MetadataIdentificationUtils.isValid(upstreamDependency),
"Upstream dependency is an invalid metadata identification string ('%s')",
upstreamDependency);
Validate.isTrue(MetadataIdentificationUtils.isValid(downstreamDependency),
"Downstream dependency is an invalid metadata identification string ('%s')",
downstreamDependency);
// Maintain the upstream-keyed map, if it even exists
final Set<String> downstream = upstreamKeyed.get(upstreamDependency);
if (downstream != null) {
downstream.remove(downstreamDependency);
}
// Maintain the downstream-keyed map, if it even exists
final Set<String> upstream = downstreamKeyed.get(downstreamDependency);
if (upstream != null) {
upstream.remove(upstreamDependency);
}
}
public Set<String> getDownstream(final String upstreamDependency) {
Validate.isTrue(MetadataIdentificationUtils.isValid(upstreamDependency),
"Upstream dependency is an invalid metadata identification string ('%s')",
upstreamDependency);
final Set<String> downstream = upstreamKeyed.get(upstreamDependency);
if (downstream == null) {
return new HashSet<String>();
}
return Collections.unmodifiableSet(new CopyOnWriteArraySet<String>(downstream));
}
public Set<String> getUpstream(final String downstreamDependency) {
Validate.isTrue(MetadataIdentificationUtils.isValid(downstreamDependency),
"Downstream dependency is an invalid metadata identification string ('%s')",
downstreamDependency);
final Set<String> upstream = downstreamKeyed.get(downstreamDependency);
if (upstream == null) {
return new HashSet<String>();
}
return Collections.unmodifiableSet(upstream);
}
public boolean isValidDependency(final String upstreamDependency,
final String downstreamDependency) {
Validate.isTrue(MetadataIdentificationUtils.isValid(upstreamDependency),
"Upstream dependency is an invalid metadata identification string ('%s')",
upstreamDependency);
Validate.isTrue(MetadataIdentificationUtils.isValid(downstreamDependency),
"Downstream dependency is an invalid metadata identification string ('%s')",
downstreamDependency);
Validate.isTrue(!upstreamDependency.equals(downstreamDependency),
"Upstream dependency cannot be the same as the downstream dependency ('%s')",
downstreamDependency);
// The simplest possible outcome is the relationship already exists, so
// quickly return in that case
Set<String> downstream = upstreamKeyed.get(upstreamDependency);
if (downstream != null && downstream.contains(downstreamDependency)) {
return true;
}
// Don't need the variable anymore, as we don't care about the other
// downstream dependencies
downstream = null;
// Need to walk the upstream dependency's parent dependency graph,
// verifying no presence of the proposed downstream dependency
// Need to build a set representing every eventual upstream dependency
// of the indicated upstream dependency
final Set<String> allUpstreams = new HashSet<String>();
buildSetOfAllUpstreamDependencies(allUpstreams, upstreamDependency);
// The dependency is valid if none of the upstreams depend on the
// proposed downstream
return !allUpstreams.contains(downstreamDependency);
}
public void notifyDownstream(final String upstreamDependency) {
try {
metadataLogger.startEvent();
if (metadataService != null) {
// First dispatch the fine-grained, instance-specific
// dependencies.
Set<String> notifiedDownstreams = new HashSet<String>();
Set<String> downstreams = getDownstream(upstreamDependency);
for (final String downstream : downstreams) {
if (metadataLogger.getTraceLevel() > 0) {
metadataLogger.log(upstreamDependency + " -> " + downstream);
}
// No need to ensure upstreamDependency is different from
// downstream, as that's taken care of in the
// isValidDependency() method
try {
final String responsibleClass =
MetadataIdentificationUtils.getMetadataClass(downstream);
metadataLogger.startTimer(responsibleClass);
metadataService.notify(upstreamDependency, downstream);
} finally {
metadataLogger.stopTimer();
}
notifiedDownstreams.add(downstream);
}
// Next dispatch the coarse-grained, class-specific
// dependencies.
// We only do it if the upstream is not class specific, as
// otherwise we'd have handled class-specific dispatch in
// previous loop
if (!MetadataIdentificationUtils.isIdentifyingClass(upstreamDependency)) {
final String asClass = MetadataIdentificationUtils.getMetadataClassId(upstreamDependency);
downstreams = getDownstream(asClass);
for (final String downstream : downstreams) {
// We don't notify a downstream if it had a direct
// instance-specific dependency and was already notified
// in previous loop
// We also don't notify if upstream is the same as
// downstream, as it doesn't make sense to notify
// yourself of an event
// (such a condition is only possible if an instance
// registered to receive class-specific notifications
// and that instance
// caused an event to fire)
if (!notifiedDownstreams.contains(downstream) && !upstreamDependency.equals(downstream)) {
if (metadataLogger.getTraceLevel() > 0) {
metadataLogger.log(upstreamDependency + " -> " + downstream + " [via class]");
}
try {
final String responsibleClass =
MetadataIdentificationUtils.getMetadataClass(downstream);
metadataLogger.startTimer(responsibleClass);
metadataService.notify(upstreamDependency, downstream);
} finally {
metadataLogger.stopTimer();
}
}
}
}
notifiedDownstreams = null;
}
// Finally dispatch the general-purpose additional listeners
for (final MetadataNotificationListener listener : listeners) {
if (metadataLogger.getTraceLevel() > 1) {
metadataLogger.log(upstreamDependency + " -> " + upstreamDependency + " ["
+ listener.getClass().getSimpleName() + "]");
}
try {
final String responsibleClass = listener.getClass().getName();
metadataLogger.startTimer(responsibleClass);
listener.notify(upstreamDependency, null);
} finally {
metadataLogger.stopTimer();
}
}
} finally {
metadataLogger.stopEvent();
}
}
public void registerDependency(final String upstreamDependency, final String downstreamDependency) {
Validate.isTrue(isValidDependency(upstreamDependency, downstreamDependency),
"Invalid dependency between upstream '%s' and downstream '%s'", upstreamDependency,
downstreamDependency);
// Maintain the upstream-keyed map
Set<String> downstream = upstreamKeyed.get(upstreamDependency);
if (downstream == null) {
downstream = new HashSet<String>();
upstreamKeyed.put(upstreamDependency, downstream);
}
downstream.add(downstreamDependency);
// Maintain the downstream-keyed map
Set<String> upstream = downstreamKeyed.get(downstreamDependency);
if (upstream == null) {
upstream = new HashSet<String>();
downstreamKeyed.put(downstreamDependency, upstream);
}
upstream.add(upstreamDependency);
}
public void removeNotificationListener(final MetadataNotificationListener listener) {
Validate.notNull(listener, "Metadata notification listener required");
if (listener instanceof MetadataService && listener.equals(metadataService)) {
metadataService = null;
return;
}
listeners.remove(listener);
}
}