content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  package org.apache.cayenne.di.spi;
  
  import java.lang.annotation.Annotation;
  import java.lang.reflect.Method;
  import java.util.Collection;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Map.Entry;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentLinkedQueue;
  import java.util.concurrent.ConcurrentMap;
  
  import org.apache.cayenne.di.BeforeScopeEnd;
  import org.apache.cayenne.di.Provider;
  import org.apache.cayenne.di.Scope;
  
  /**
   * An implementation of a DI scopes with support scope events.
   * 
   * @since 3.1
   */
  public class DefaultScope implements Scope {
  
      protected Collection<Class<? extends Annotation>> eventTypes;
      protected ConcurrentMap<String, Collection<ScopeEventBinding>> listeners;
  
      private static final String SPECIAL_EVENT = AfterScopeEnd.class.getName();
  
      @SafeVarargs
  	public DefaultScope(Class<? extends Annotation>... customEventTypes) {
          this.listeners = new ConcurrentHashMap<>();
          this.eventTypes = new HashSet<Class<? extends Annotation>>();
  
          // initialize the event listener data structures in constructor to avoid
          // synchronization concerns on everything but per-event lists.
  
          // standard event types
          eventTypes.add(BeforeScopeEnd.class);
          eventTypes.add(AfterScopeEnd.class);
  
          // custom event types
          if (customEventTypes != null) {
              for (Class<? extends Annotation> type : customEventTypes) {
                  eventTypes.add(type);
              }
          }
  
          for (Class<? extends Annotation> type : eventTypes) {
              listeners.put(type.getName(), new ConcurrentLinkedQueue<ScopeEventBinding>());
          }
      }
  
      /**
       * Shuts down this scope, posting {@link BeforeScopeEnd} and {@link AfterScopeEnd}
       * events.
       */
      public void shutdown() {
          postScopeEvent(BeforeScopeEnd.class);
  
          // this will notify providers that they should reset their state and unregister
          // object event listeners that just went out of scope
          postScopeEvent(AfterScopeEnd.class);
      }
  
      /**
       * Registers annotated methods of an arbitrary object for this scope lifecycle events.
       */
      public void addScopeEventListener(Object object) {
  
          // TODO: cache metadata for non-singletons scopes for performance
  
          // 'getMethods' grabs public method from the class and its superclasses...
          for (Method method : object.getClass().getMethods()) {
  
              for (Class<? extends Annotation> annotationType : eventTypes) {
  
                  if (method.isAnnotationPresent(annotationType)) {
                      String typeName = annotationType.getName();
  
                      Collection<ScopeEventBinding> eventListeners = listeners
                              .get(typeName);
                      eventListeners.add(new ScopeEventBinding(object, method));
                  }
              }
          }
      }
  
      public void removeScopeEventListener(Object object) {
  
          // TODO: 2 level-deep full scan will not be very efficient for short scopes. Right
          // now this would only affect the unit test scope, but if we start creating the
          // likes of HTTP request scope, we may need to create a faster listener
          // removal algorithm.
  
          for (Entry<String, Collection<ScopeEventBinding>> entry : listeners.entrySet()) {
  
              if (SPECIAL_EVENT.equals(entry.getKey())) {
                  // no scanning and removal of Scope providers ...
                  // for faster scan skip those
                  continue;
              }
  
              Iterator<ScopeEventBinding> it = entry.getValue().iterator();
              while (it.hasNext()) {
                  ScopeEventBinding binding = it.next();
                  if (binding.getObject() == object) {
                      it.remove();
                  }
              }
          }
      }
  
      /**
       * Posts a scope event to all registered listeners. There's no predetermined order of
       * event dispatching. An exception thrown by any of the listeners stops further event
       * processing and is rethrown.
       */
      public void postScopeEvent(
              Class<? extends Annotation> type,
              Object... eventParameters) {
  
          Collection<ScopeEventBinding> eventListeners = listeners.get(type.getName());
  
          if (eventListeners != null) {
              Iterator<ScopeEventBinding> it = eventListeners.iterator();
              while (it.hasNext()) {
                  ScopeEventBinding listener = it.next();
                  if (!listener.onScopeEvent(eventParameters)) {
                      // remove listeners that were garbage collected
                      it.remove();
                  }
              }
          }
      }
  
      @Override
      public <T> Provider<T> scope(Provider<T> unscoped) {
          return new DefaultScopeProvider<T>(this, unscoped);
      }
  }
  