001 package org.apache.fulcrum.yaafi.service.reconfiguration;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.io.File;
023 import java.io.InputStream;
024 import java.security.MessageDigest;
025
026 import org.apache.avalon.framework.activity.Disposable;
027 import org.apache.avalon.framework.activity.Initializable;
028 import org.apache.avalon.framework.activity.Startable;
029 import org.apache.avalon.framework.activity.Suspendable;
030 import org.apache.avalon.framework.configuration.Configuration;
031 import org.apache.avalon.framework.configuration.ConfigurationException;
032 import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
033 import org.apache.avalon.framework.configuration.Reconfigurable;
034 import org.apache.avalon.framework.context.Context;
035 import org.apache.avalon.framework.context.ContextException;
036 import org.apache.avalon.framework.context.Contextualizable;
037 import org.apache.avalon.framework.logger.AbstractLogEnabled;
038 import org.apache.avalon.framework.service.ServiceException;
039 import org.apache.avalon.framework.service.ServiceManager;
040 import org.apache.avalon.framework.service.Serviceable;
041 import org.apache.fulcrum.yaafi.framework.container.ServiceLifecycleManager;
042
043
044 /**
045 * Monitors the componentConfiguration.xml and triggers a reconfiguration
046 * if the content of the component configuration file has changed.
047 *
048 * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
049 */
050
051 public class ReconfigurationServiceImpl
052 extends AbstractLogEnabled
053 implements ReconfigurationService, Serviceable, Contextualizable,
054 Reconfigurable, Initializable, Runnable, Startable, Disposable
055 {
056 /** the interval between two checks in ms */
057 private int interval;
058
059 /** shall the worker thread terminate immediately */
060 private boolean terminateNow;
061
062 /** the worker thread polling the componentConfiguraton */
063 private Thread workerThread;
064
065 /** the ServiceManager to use */
066 private ServiceManager serviceManager;
067
068 /** the application directory */
069 private File applicationDir;
070
071 /** our list of resources to monitor */
072 private ReconfigurationEntry[] reconfigurationEntryList;
073
074 /** the interface to reconfigure individual services */
075 private ServiceLifecycleManager serviceLifecycleManager;
076
077 /////////////////////////////////////////////////////////////////////////
078 // Avalon Service Lifecycle Implementation
079 /////////////////////////////////////////////////////////////////////////
080
081 /**
082 * Constructor
083 */
084 public ReconfigurationServiceImpl()
085 {
086 this.terminateNow = false;
087 }
088
089 /**
090 * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
091 */
092 public void service(ServiceManager manager) throws ServiceException
093 {
094 this.serviceManager = manager;
095 this.serviceLifecycleManager = (ServiceLifecycleManager) manager;
096 }
097
098 /**
099 * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
100 */
101 public void contextualize(Context context) throws ContextException
102 {
103 this.applicationDir = (File) context.get("urn:avalon:home");
104 }
105
106 /**
107 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
108 */
109 public void configure(Configuration configuration) throws ConfigurationException
110 {
111 // limit to minimum interval of 1 second
112
113 this.interval = Math.max( configuration.getAttributeAsInteger("interval",5000), 1000 );
114
115 this.getLogger().debug( "Monitoring the resources every " + this.interval + " ms" );
116
117 // parse the resources to monitor
118
119 Configuration entry = null;
120 Configuration services = null;
121 Configuration[] serviceEntries = null;
122 Configuration[] entryList = configuration.getChildren("entry");
123
124 String location = null;
125 String serviceName = null;
126 String[] serviceNameList = null;
127 ReconfigurationEntry reconfigurationEntry = null;
128 ReconfigurationEntry[] list = new ReconfigurationEntry[entryList.length];
129
130 for( int i=0; i<entryList.length; i++ )
131 {
132 entry = entryList[i];
133 location = entry.getChild("location").getValue();
134 services = entry.getChild("services",false);
135
136 this.getLogger().debug( "Adding the following resource to monitor : " + location );
137
138 if( services != null )
139 {
140 serviceEntries = services.getChildren("service");
141 serviceNameList = new String[serviceEntries.length];
142
143 for( int j=0; j<serviceEntries.length; j++ )
144 {
145 serviceName = serviceEntries[j].getAttribute("name");
146 serviceNameList[j] = serviceName;
147 }
148 }
149
150 reconfigurationEntry = new ReconfigurationEntry(
151 this.getLogger(),
152 this.applicationDir,
153 location,
154 serviceNameList
155 );
156
157 list[i] = reconfigurationEntry;
158 }
159
160 this.getLogger().debug( "Monitoring " + list.length + " resources" );
161
162 this.setReconfigurationEntryList(list);
163 }
164
165 /**
166 * @see org.apache.avalon.framework.activity.Initializable#initialize()
167 */
168 public void initialize() throws Exception
169 {
170 // request a SHA-1 to make sure that it is supported
171
172 MessageDigest.getInstance( "SHA1" );
173
174 // check that the ServiceManager inplements Reconfigurable
175
176 if( (this.serviceManager instanceof ServiceLifecycleManager) == false )
177 {
178 String msg = "The ServiceManager instance does not implement ServiceLifecycleManager?!";
179 throw new IllegalArgumentException( msg );
180 }
181
182 // create the worker thread polling the target
183
184 this.workerThread = new Thread( this, "ReconfigurationService" );
185 }
186
187 /**
188 * @see org.apache.avalon.framework.activity.Startable#start()
189 */
190 public void start() throws Exception
191 {
192 this.getLogger().debug( "Starting worker thread ..." );
193 this.workerThread.start();
194 }
195
196 /**
197 * @see org.apache.avalon.framework.activity.Startable#stop()
198 */
199 public void stop() throws Exception
200 {
201 this.getLogger().debug( "Stopping worker thread ..." );
202 this.terminateNow = true;
203 this.workerThread.interrupt();
204 this.workerThread.join( 10000 );
205 }
206
207 /**
208 * @see org.apache.avalon.framework.activity.Disposable#dispose()
209 */
210 public void dispose()
211 {
212 this.terminateNow = false;
213 this.applicationDir = null;
214 this.workerThread = null;
215 this.serviceManager = null;
216 this.reconfigurationEntryList = null;
217 }
218
219 /**
220 * @see org.apache.avalon.framework.configuration.Reconfigurable#reconfigure(org.apache.avalon.framework.configuration.Configuration)
221 */
222 public void reconfigure(Configuration configuration)
223 throws ConfigurationException
224 {
225 this.configure(configuration);
226 }
227
228 /////////////////////////////////////////////////////////////////////////
229 // Service interface implementation
230 /////////////////////////////////////////////////////////////////////////
231
232 /**
233 * Polls for changes in the confguration to reconfigure either the
234 * whole container or just a list of services.
235 *
236 * @see java.lang.Runnable#run()
237 */
238 public void run()
239 {
240 ReconfigurationEntry reconfigurationEntry = null;
241 ReconfigurationEntry[] list = null;
242
243 while( this.terminateNow == false )
244 {
245 list = this.getReconfigurationEntryList();
246
247 try
248 {
249 for( int i=0; i<list.length; i++ )
250 {
251 reconfigurationEntry = list[i];
252
253 if( reconfigurationEntry.hasChanged() )
254 {
255 this.onReconfigure( reconfigurationEntry );
256 }
257 }
258
259 Thread.sleep( this.interval );
260 }
261 catch( InterruptedException e )
262 {
263 continue;
264 }
265 catch(Exception e)
266 {
267 String msg = "The ReconfigurationService had a problem";
268 this.getLogger().error(msg,e);
269 continue;
270 }
271 }
272 }
273
274 /////////////////////////////////////////////////////////////////////////
275 // Service implementation
276 /////////////////////////////////////////////////////////////////////////
277
278 /**
279 * Reconfigure either the whole container or a list of services. This
280 * method is called within a seperate worker thred.
281 *
282 * @param reconfigurationEntry the configuration what to reconfigure
283 * @throws Exception the reconfiguration failed
284 */
285 protected void onReconfigure( ReconfigurationEntry reconfigurationEntry )
286 throws Exception
287 {
288 if( reconfigurationEntry.getServiceList() == null )
289 {
290 // reconfigure the whole container using Avalon Lifecycle Spec
291
292 InputStream is = reconfigurationEntry.locate();
293 DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
294 Configuration configuration = builder.build(is);
295 is.close();
296 is = null;
297
298 this.getLogger().warn( "Starting to reconfigure the container" );
299
300 if( this.serviceManager instanceof Suspendable)
301 {
302 this.getLogger().info( "Calling suspend() of the container" );
303 ((Suspendable) this.serviceManager).suspend();
304 }
305
306 if( this.serviceManager instanceof Reconfigurable)
307 {
308 this.getLogger().info( "Calling reconfigure() of the container" );
309 ((Reconfigurable) this.serviceManager).reconfigure(configuration);
310 }
311
312 if( this.serviceManager instanceof Suspendable)
313 {
314 this.getLogger().info( "Calling resume() of the container" );
315 ((Suspendable) this.serviceManager).resume();
316 }
317
318 this.getLogger().info( "Reconfiguring the container was successful" );
319 }
320 else
321 {
322 String[] serviceList = reconfigurationEntry.getServiceList();
323 this.getLogger().warn( "Calling reconfigure() on individual services : " + serviceList.length );
324 this.serviceLifecycleManager.reconfigure(serviceList);
325 }
326 }
327
328 /**
329 * @return Returns the reconfigurationEntryList.
330 */
331 private synchronized ReconfigurationEntry [] getReconfigurationEntryList()
332 {
333 return reconfigurationEntryList;
334 }
335
336 /**
337 * @param reconfigurationEntryList The reconfigurationEntryList to set.
338 */
339 private synchronized void setReconfigurationEntryList(
340 ReconfigurationEntry [] reconfigurationEntryList)
341 {
342 this.reconfigurationEntryList = reconfigurationEntryList;
343 }
344 }