View Javadoc

1   /*
2    * Licensed to the University Corporation for Advanced Internet Development, 
3    * Inc. (UCAID) under one or more contributor license agreements.  See the 
4    * NOTICE file distributed with this work for additional information regarding
5    * copyright ownership. The UCAID licenses this file to You under the Apache 
6    * License, Version 2.0 (the "License"); you may not use this file except in 
7    * compliance with the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package edu.internet2.middleware.shibboleth.common.config;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.concurrent.locks.Lock;
24  import java.util.concurrent.locks.ReadWriteLock;
25  import java.util.concurrent.locks.ReentrantReadWriteLock;
26  
27  import org.opensaml.util.resource.Resource;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.beans.factory.BeanNameAware;
31  import org.springframework.context.ApplicationContext;
32  import org.springframework.context.ApplicationContextAware;
33  import org.springframework.context.support.GenericApplicationContext;
34  
35  import edu.internet2.middleware.shibboleth.common.service.Service;
36  import edu.internet2.middleware.shibboleth.common.service.ServiceException;
37  
38  /**
39   * A service whose Spring beans are loaded into a service specific {@link ApplicationContext} that is a child of the
40   * context provided in {@link #setApplicationContext(ApplicationContext)}.
41   * 
42   * Services derived from this base class may not be re-initialized after they have been destroyed.
43   */
44  public abstract class BaseService implements Service, ApplicationContextAware, BeanNameAware {
45  
46      /** Class logger. */
47      private final Logger log = LoggerFactory.getLogger(BaseService.class);
48  
49      /** Unique name of this service. */
50      private String serviceName;
51  
52      /** Read/Write lock for the context. */
53      private ReentrantReadWriteLock serviceContextRWLock;
54  
55      /** Application context owning this engine. */
56      private ApplicationContext owningContext;
57  
58      /** Context containing loaded with service content. */
59      private GenericApplicationContext serviceContext;
60  
61      /** List of configuration resources for this service. */
62      private ArrayList<Resource> serviceConfigurations;
63  
64      /** Indicates if the service has been initialized already. */
65      private boolean isInitialized;
66  
67      /** Indicates if the service has been destroyed. */
68      private boolean isDestroyed;
69  
70      /** Constructor. */
71      public BaseService() {
72          serviceContextRWLock = new ReentrantReadWriteLock(true);
73          isInitialized = false;
74      }
75  
76      /** {@inheritDoc} */
77      public void destroy() throws ServiceException {
78          Lock writeLock = getReadWriteLock().writeLock();
79          writeLock.lock();
80          isDestroyed = true;
81          serviceContext = null;
82          serviceConfigurations.clear();
83          setInitialized(false);
84          writeLock.unlock();
85          serviceContextRWLock = null;
86      }
87  
88      /**
89       * Gets the application context that is the parent to this service's context.
90       * 
91       * @return application context that is the parent to this service's context
92       */
93      public ApplicationContext getApplicationContext() {
94          return owningContext;
95      }
96  
97      /** {@inheritDoc} */
98      public String getId() {
99          return serviceName;
100     }
101     
102     /**
103      * Gets the read-write lock guarding the service context.
104      * 
105      * @return read-write lock guarding the service context
106      */
107     protected ReadWriteLock getReadWriteLock() {
108         return serviceContextRWLock;
109     }
110     
111     /**
112      * Gets an unmodifiable list of configurations for this service.
113      * 
114      * @return unmodifiable list of configurations for this service
115      */
116     public List<Resource> getServiceConfigurations(){
117         return Collections.unmodifiableList(serviceConfigurations);
118     }
119 
120     /**
121      * Gets this service's context.
122      * 
123      * @return this service's context
124      */
125     public ApplicationContext getServiceContext() {
126         return serviceContext;
127     }
128 
129     /** {@inheritDoc} */
130     public void initialize() throws ServiceException {
131         if (isDestroyed()) {
132             throw new SecurityException(getId() + " service has been destroyed, it may not be initialized.");
133         }
134 
135         if (isInitialized()) {
136             return;
137         }
138         
139         loadContext();
140     }
141 
142     /** {@inheritDoc} */
143     public boolean isInitialized() {
144         return isInitialized;
145     }
146     
147     /** {@inheritDoc} */
148     public boolean isDestroyed() {
149         return isDestroyed;
150     }
151 
152     /**
153      * Loads the service context.
154      * 
155      * @throws ServiceException thrown if the configuration for this service could not be loaded
156      */
157     protected void loadContext() throws ServiceException {
158         log.info("Loading new configuration for service {}", getId());
159         
160         if(serviceConfigurations == null || serviceConfigurations.isEmpty()){
161             setInitialized(true);
162             return;
163         }
164         
165         GenericApplicationContext newServiceContext = new GenericApplicationContext(getApplicationContext());
166         newServiceContext.setDisplayName("ApplicationContext:" + getId());
167         Lock writeLock = getReadWriteLock().writeLock();
168         writeLock.lock();
169         try {
170             SpringConfigurationUtils.populateRegistry(newServiceContext, getServiceConfigurations());
171             newServiceContext.refresh();
172 
173             GenericApplicationContext replacedServiceContext = serviceContext;
174             onNewContextCreated(newServiceContext);
175             setServiceContext(newServiceContext);
176             setInitialized(true);
177             if(replacedServiceContext != null){
178                 replacedServiceContext.close();
179             }
180             log.info("{} service loaded new configuration", getId());
181         } catch (Throwable e) {
182             // Here we catch all the other exceptions thrown by Spring when it starts up the context
183             setInitialized(false);
184             Throwable rootCause = e;
185             while (rootCause.getCause() != null) {
186                 rootCause = rootCause.getCause();
187             }
188             log.error("Configuration was not loaded for " + getId()
189                     + " service, error creating components.  The root cause of this error was: " +
190                     rootCause.getClass().getCanonicalName() + ": " + rootCause.getMessage());
191             log.trace("Full stacktrace is: ", e);
192             throw new ServiceException("Configuration was not loaded for " + getId()
193                     + " service, error creating components.", rootCause);
194         }finally{
195             writeLock.unlock();
196         }
197     }
198 
199     /**
200      * Called after a new context has been created but before it set as the service's context. If an exception is thrown
201      * the new context will not be set as the service's context and the current service context will be retained.
202      * 
203      * @param newServiceContext the newly created context for the service
204      * 
205      * @throws ServiceException thrown if there is a problem with the given service context
206      */
207     protected abstract void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException;
208 
209     /**
210      * Sets the application context that is the parent to this service's context.
211      * 
212      * {@inheritDoc}
213      */
214     public void setApplicationContext(ApplicationContext applicationContext) {
215         owningContext = applicationContext;
216     }
217 
218     /** {@inheritDoc} */
219     public void setBeanName(String name) {
220         serviceName = name;
221     }
222 
223     /**
224      * Sets whether this service has been initialized.
225      * 
226      * @param initialized whether this service has been initialized
227      */
228     protected void setInitialized(boolean initialized) {
229         isInitialized = initialized;
230     }
231 
232     /**
233      * Sets the service's configuration resources.
234      * 
235      * @param configurations configuration resources for the service
236      */
237     public void setServiceConfigurations(List<Resource> configurations) {
238         if(isInitialized){
239             throw new IllegalStateException("Service already initialized");
240         }
241         serviceConfigurations = new ArrayList<Resource>(configurations);
242     }
243 
244     /**
245      * Sets this service's context.
246      * 
247      * @param context this service's context
248      */
249     protected void setServiceContext(GenericApplicationContext context) {
250         serviceContext = context;
251     }
252 }