public final void notify(final String upstreamDependency, String downstreamDependency) {
    if (downstreamDependency == null) {
      notifyForGenericListener(upstreamDependency);
      return;
    }

    // Handle if the downstream dependency is "class level",  meaning we need to figure out the
    // specific downstream MID this metadata provider wants to update/refresh.
    if (MetadataIdentificationUtils.isIdentifyingClass(downstreamDependency)) {
      // We have not identified an instance-specific downstream MID, so we'll need to calculate an
      // instance-specific downstream MID to retrieve.
      downstreamDependency = resolveDownstreamDependencyIdentifier(upstreamDependency);

      // We skip if the resolution method returns null, as it doesn't want to continue for some
      // reason
      if (downstreamDependency == null) {
        return;
      }

      Assert.isTrue(
          MetadataIdentificationUtils.isIdentifyingInstance(downstreamDependency),
          "An instance-specific downstream MID was required by '"
              + getClass().getName()
              + "' (not '"
              + downstreamDependency
              + "')");

      // We only need to proceed if the downstream dependency relationship is not already
      // registered.
      // It is unusual to register a direct downstream relationship given it costs dependency
      // registration memory and class-level notifications will always occur anyway.
      if (metadataDependencyRegistry
          .getDownstream(upstreamDependency)
          .contains(downstreamDependency)) {
        return;
      }
    }

    // We should now have an instance-specific "downstream dependency" that can be processed by this
    // class
    Assert.isTrue(
        MetadataIdentificationUtils.getMetadataClass(downstreamDependency)
            .equals(MetadataIdentificationUtils.getMetadataClass(getProvidesType())),
        "Unexpected downstream notification for '"
            + downstreamDependency
            + "' to this provider (which uses '"
            + getProvidesType()
            + "'");

    // We no longer notify downstreams here, as the "get" operation with eviction will ensure the
    // main get(String) method below will be fired and it
    // directly notified downstreams as part of that method (BPA 10 Dec 2010)
    metadataService.get(downstreamDependency, true);
  }
  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>();
        for (final String downstream : getDownstream(upstreamDependency)) {
          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);
          for (final String downstream : getDownstream(asClass)) {
            // 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 notify(String upstreamDependency, String downstreamDependency) {
    ProjectMetadata projectMetadata = projectOperations.getProjectMetadata();
    if (projectMetadata == null) {
      return;
    }

    if (MetadataIdentificationUtils.isIdentifyingClass(downstreamDependency)) {
      Assert.isTrue(
          MetadataIdentificationUtils.getMetadataClass(upstreamDependency)
              .equals(
                  MetadataIdentificationUtils.getMetadataClass(
                      PhysicalTypeIdentifier.getMetadataIdentiferType())),
          "Expected class-level notifications only for PhysicalTypeIdentifier (not '"
              + upstreamDependency
              + "')");

      ClassOrInterfaceTypeDetails cid =
          typeLocationService.getTypeForIdentifier(upstreamDependency);
      boolean processed = false;
      if (MemberFindingUtils.getAnnotationOfType(cid.getAnnotations(), RooJavaType.ROO_GWT_REQUEST)
          != null) {
        ClassOrInterfaceTypeDetails proxy = gwtTypeService.lookupProxyFromRequest(cid);
        if (proxy != null) {
          JavaType typeName = PhysicalTypeIdentifier.getJavaType(proxy.getDeclaredByMetadataId());
          Path typePath = PhysicalTypeIdentifier.getPath(proxy.getDeclaredByMetadataId());
          downstreamDependency = GwtLocatorMetadata.createIdentifier(typeName, typePath);
          processed = true;
        }
      }
      if (!processed
          && MemberFindingUtils.getAnnotationOfType(cid.getAnnotations(), RooJavaType.ROO_GWT_PROXY)
              == null) {
        boolean found = false;
        for (ClassOrInterfaceTypeDetails classOrInterfaceTypeDetails :
            typeLocationService.findClassesOrInterfaceDetailsWithAnnotation(
                RooJavaType.ROO_GWT_PROXY)) {
          AnnotationMetadata annotationMetadata =
              GwtUtils.getFirstAnnotation(
                  classOrInterfaceTypeDetails, GwtUtils.ROO_PROXY_REQUEST_ANNOTATIONS);
          if (annotationMetadata != null) {
            AnnotationAttributeValue<?> attributeValue = annotationMetadata.getAttribute("value");
            if (attributeValue != null) {
              String mirrorName = GwtUtils.getStringValue(attributeValue);
              if (mirrorName != null
                  && cid.getName().getFullyQualifiedTypeName().equals(attributeValue.getValue())) {
                found = true;
                JavaType typeName =
                    PhysicalTypeIdentifier.getJavaType(
                        classOrInterfaceTypeDetails.getDeclaredByMetadataId());
                Path typePath =
                    PhysicalTypeIdentifier.getPath(
                        classOrInterfaceTypeDetails.getDeclaredByMetadataId());
                downstreamDependency = GwtLocatorMetadata.createIdentifier(typeName, typePath);
                break;
              }
            }
          }
        }
        if (!found) {
          return;
        }
      } else if (!processed) {
        // A physical Java type has changed, and determine what the corresponding local metadata
        // identification string would have been
        JavaType typeName = PhysicalTypeIdentifier.getJavaType(upstreamDependency);
        Path typePath = PhysicalTypeIdentifier.getPath(upstreamDependency);
        downstreamDependency = GwtLocatorMetadata.createIdentifier(typeName, typePath);
      }

      // 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 (metadataDependencyRegistry
          .getDownstream(upstreamDependency)
          .contains(downstreamDependency)) {
        return;
      }
    }

    // We should now have an instance-specific "downstream dependency" that can be processed by this
    // class
    Assert.isTrue(
        MetadataIdentificationUtils.getMetadataClass(downstreamDependency)
            .equals(MetadataIdentificationUtils.getMetadataClass(getProvidesType())),
        "Unexpected downstream notification for '"
            + downstreamDependency
            + "' to this provider (which uses '"
            + getProvidesType()
            + "'");

    metadataService.get(downstreamDependency, true);
  }