View Javadoc

1   package org.apache.onami.spi.services;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static java.lang.String.format;
23  import static java.lang.Thread.currentThread;
24  import static org.apache.onami.spi.core.ServiceLoader.load;
25  
26  import java.lang.annotation.Annotation;
27  import java.util.Iterator;
28  import java.util.LinkedList;
29  import java.util.List;
30  
31  import javax.inject.Qualifier;
32  
33  import com.google.inject.AbstractModule;
34  import com.google.inject.BindingAnnotation;
35  import com.google.inject.ProvisionException;
36  import com.google.inject.binder.AnnotatedBindingBuilder;
37  import com.google.inject.binder.LinkedBindingBuilder;
38  
39  /**
40   * A Google Guice {@code Module} to simplify the task of
41   * binding Services to Providers using the SPI pattern.
42   */
43  public abstract class ServiceLoaderModule
44      extends AbstractModule
45  {
46  
47      private List<ServiceInfo<?>> services = new LinkedList<ServiceInfo<?>>();
48  
49      @Override
50      protected final void configure()
51      {
52          if ( !services.isEmpty() )
53          {
54              throw new IllegalStateException( "Re-entry is not allowed." );
55          }
56  
57          configureServices();
58  
59          try
60          {
61              for ( ServiceInfo<?> builder : services )
62              {
63                  bindService( builder );
64              }
65          }
66          finally
67          {
68              services.clear();
69          }
70      }
71  
72      private <S> void bindService( ServiceInfo<S> serviceInfo )
73      {
74          Class<S> serviceType = serviceInfo.getServiceType();
75  
76          Iterator<Class<? extends S>> servicesIterator = load( serviceType, serviceInfo.getClassLoader() )
77                                                          .iterator();
78          boolean found = false;
79          while ( servicesIterator.hasNext() )
80          {
81              if ( !found )
82              {
83                  found = true;
84              }
85  
86              Class<? extends S> serviceImplType = servicesIterator.next();
87  
88              bindService( serviceType, serviceImplType );
89          }
90  
91          if ( !found )
92          {
93              throw new ProvisionException( format( "No Provider found for Service %s", serviceType.getName() ) );
94          }
95      }
96  
97      private <S> void bindService( Class<S> serviceType, Class<? extends S> serviceImplType )
98      {
99          AnnotatedBindingBuilder<S> annotatedBindingBuilder = bind( serviceType );
100         LinkedBindingBuilder<S> linkedBindingBuilder = annotatedBindingBuilder;
101 
102         dance: for ( Annotation annotation : serviceImplType.getAnnotations() )
103         {
104             Class<? extends Annotation> annotationType = annotation.annotationType();
105 
106             /*
107              * if the serviceImplType is a javax.inject.Qualifier annotation
108              * or
109              * if the serviceImplType is a com.google.inject.BindingAnnotation
110              */
111             if ( annotationType.isAnnotationPresent( Qualifier.class )
112                  || annotationType.isAnnotationPresent( BindingAnnotation.class ) )
113             {
114                 linkedBindingBuilder = annotatedBindingBuilder.annotatedWith( annotation );
115                 break dance;
116             }
117         }
118 
119         linkedBindingBuilder.to( serviceImplType );
120     }
121 
122     protected abstract void configureServices();
123 
124     /**
125      * EDSL to bind Services to Providers using the SPI pattern.
126      *
127      * @param service The type of the service to be loaded.
128      * @return the chained EDSL builder.
129      */
130     protected final <S> FromClassLoaderBuilder discover( Class<S> service )
131     {
132         checkArgument( service != null, "Impossible to bind null service class!" );
133         ServiceInfo<S> builder = new ServiceInfo<S>( service );
134         services.add( builder );
135         return builder;
136     }
137 
138     /**
139      * EDSL for SPI implementation.
140      *
141      * @param <S> The type of the service to be loaded.
142      */
143     private static final class ServiceInfo<S>
144         implements FromClassLoaderBuilder
145     {
146 
147         private final Class<S> serviceType;
148 
149         private ClassLoader classLoader;
150 
151         public ServiceInfo( Class<S> serviceType )
152         {
153             this.serviceType = serviceType;
154             classLoader = currentThread().getContextClassLoader();
155         }
156 
157         /**
158          * {@inheritDoc}
159          */
160         public void fromClassLoader( ClassLoader classLoader )
161         {
162             checkArgument( classLoader != null,
163                            "Impossible to load Service %s with a null ClassLoader", serviceType.getName() );
164             this.classLoader = classLoader;
165         }
166 
167         public Class<S> getServiceType()
168         {
169             return serviceType;
170         }
171 
172         public ClassLoader getClassLoader()
173         {
174             return classLoader;
175         }
176 
177     }
178 
179     private static void checkArgument( boolean expression, String errorMessagePattern, Object...args )
180     {
181         if ( !expression )
182         {
183             throw new IllegalArgumentException( format( errorMessagePattern, args ) );
184         }
185     }
186 
187 }