View Javadoc
1   package org.apache.fulcrum.yaafi.service.reconfiguration;
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 java.io.File;
23  import java.io.InputStream;
24  import java.security.MessageDigest;
25  
26  import org.apache.avalon.framework.activity.Disposable;
27  import org.apache.avalon.framework.activity.Initializable;
28  import org.apache.avalon.framework.activity.Startable;
29  import org.apache.avalon.framework.activity.Suspendable;
30  import org.apache.avalon.framework.configuration.Configuration;
31  import org.apache.avalon.framework.configuration.ConfigurationException;
32  import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
33  import org.apache.avalon.framework.configuration.Reconfigurable;
34  import org.apache.avalon.framework.context.Context;
35  import org.apache.avalon.framework.context.ContextException;
36  import org.apache.avalon.framework.context.Contextualizable;
37  import org.apache.avalon.framework.logger.AbstractLogEnabled;
38  import org.apache.avalon.framework.service.ServiceException;
39  import org.apache.avalon.framework.service.ServiceManager;
40  import org.apache.avalon.framework.service.Serviceable;
41  import org.apache.fulcrum.yaafi.framework.container.ServiceLifecycleManager;
42  
43  /**
44   * Monitors the componentConfiguration.xml and triggers a reconfiguration if the
45   * content of the component configuration file has changed.
46   *
47   * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
48   */
49  
50  public class ReconfigurationServiceImpl extends AbstractLogEnabled implements ReconfigurationService, Serviceable,
51  		Contextualizable, Reconfigurable, Initializable, Runnable, Startable, Disposable {
52  	/** the interval between two checks in ms */
53  	private int interval;
54  
55  	/** shall the worker thread terminate immediately */
56  	private boolean terminateNow;
57  
58  	/** the worker thread polling the componentConfiguraton */
59  	private Thread workerThread;
60  
61  	/** the ServiceManager to use */
62  	private ServiceManager serviceManager;
63  
64  	/** the application directory */
65  	private File applicationDir;
66  
67  	/** our list of resources to monitor */
68  	private ReconfigurationEntry[] reconfigurationEntryList;
69  
70  	/** the interface to reconfigure individual services */
71  	private ServiceLifecycleManager serviceLifecycleManager;
72  
73  	/////////////////////////////////////////////////////////////////////////
74  	// Avalon Service Lifecycle Implementation
75  	/////////////////////////////////////////////////////////////////////////
76  
77  	/**
78  	 * Constructor
79  	 */
80  	public ReconfigurationServiceImpl() {
81  		this.terminateNow = false;
82  	}
83  
84  	/**
85  	 * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
86  	 */
87  	public void service(ServiceManager manager) throws ServiceException {
88  		this.serviceManager = manager;
89  		this.serviceLifecycleManager = (ServiceLifecycleManager) manager;
90  	}
91  
92  	/**
93  	 * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
94  	 */
95  	public void contextualize(Context context) throws ContextException {
96  		this.applicationDir = (File) context.get("urn:avalon:home");
97  	}
98  
99  	/**
100 	 * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
101 	 */
102 	public void configure(Configuration configuration) throws ConfigurationException {
103 		// limit to minimum interval of 1 second
104 
105 		this.interval = Math.max(configuration.getAttributeAsInteger("interval", 5000), 1000);
106 
107 		this.getLogger().debug("Monitoring the resources every " + this.interval + " ms");
108 
109 		// parse the resources to monitor
110 
111 		// Configuration entry = null;
112 		Configuration services = null;
113 		Configuration[] serviceEntries = null;
114 		Configuration[] entryList = configuration.getChildren("entry");
115 
116 		String location = null;
117 		String serviceName = null;
118 		String[] serviceNameList = null;
119 		ReconfigurationEntry reconfigurationEntry = null;
120 		ReconfigurationEntry[] list = new ReconfigurationEntry[entryList.length];
121 		int listIndex = 0;
122 		for (Configuration entry : entryList) {
123 			location = entry.getChild("location").getValue();
124 			services = entry.getChild("services", false);
125 
126 			this.getLogger().debug("Adding the following resource to monitor : " + location);
127 
128 			if (services != null) {
129 				serviceEntries = services.getChildren("service");
130 				serviceNameList = new String[serviceEntries.length];
131 
132 				for (int j = 0; j < serviceEntries.length; j++) {
133 					serviceName = serviceEntries[j].getAttribute("name");
134 					serviceNameList[j] = serviceName;
135 				}
136 			}
137 
138 			reconfigurationEntry = new ReconfigurationEntry(this.getLogger(), this.applicationDir, location,
139 					serviceNameList);
140 
141 			list[listIndex++] = reconfigurationEntry;
142 		}
143 
144 		this.getLogger().debug("Monitoring " + list.length + " resources");
145 
146 		this.setReconfigurationEntryList(list);
147 	}
148 
149 	/**
150 	 * @see org.apache.avalon.framework.activity.Initializable#initialize()
151 	 */
152 	public void initialize() throws Exception {
153 		// request a SHA-1 to make sure that it is supported
154 		MessageDigest.getInstance("SHA1");
155 
156 		// check that the ServiceManager implements Reconfigurable
157 		if (this.serviceManager instanceof ServiceLifecycleManager == false)
158 			throw new IllegalArgumentException(
159 					"The ServiceManager instance does not implement ServiceLifecycleManager!");
160 
161 		// create the worker thread polling the target
162 		this.workerThread = new Thread(this, "ReconfigurationService");
163 	}
164 
165 	/**
166 	 * @see org.apache.avalon.framework.activity.Startable#start()
167 	 */
168 	public void start() throws Exception {
169 		this.getLogger().debug("Starting worker thread ...");
170 		this.workerThread.start();
171 	}
172 
173 	/**
174 	 * @see org.apache.avalon.framework.activity.Startable#stop()
175 	 */
176 	public void stop() throws Exception {
177 		this.getLogger().debug("Stopping worker thread ...");
178 		this.terminateNow = true;
179 		this.workerThread.interrupt();
180 		this.workerThread.join(10000);
181 	}
182 
183 	/**
184 	 * @see org.apache.avalon.framework.activity.Disposable#dispose()
185 	 */
186 	public void dispose() {
187 		this.terminateNow = false;
188 		this.applicationDir = null;
189 		this.workerThread = null;
190 		this.serviceManager = null;
191 		this.reconfigurationEntryList = null;
192 	}
193 
194 	/**
195 	 * @see org.apache.avalon.framework.configuration.Reconfigurable#reconfigure(org.apache.avalon.framework.configuration.Configuration)
196 	 */
197 	public void reconfigure(Configuration configuration) throws ConfigurationException {
198 		this.configure(configuration);
199 	}
200 
201 	/////////////////////////////////////////////////////////////////////////
202 	// Service interface implementation
203 	/////////////////////////////////////////////////////////////////////////
204 
205 	/**
206 	 * Polls for changes in the confguration to reconfigure either the whole
207 	 * container or just a list of services.
208 	 *
209 	 * @see java.lang.Runnable#run()
210 	 */
211 	public void run() {
212 		ReconfigurationEntry[] list = null;
213 		while (this.terminateNow == false) {
214 			list = this.getReconfigurationEntryList();
215 			try {
216 				for (ReconfigurationEntry reconfigurationEntry : list) {
217 					if (reconfigurationEntry.hasChanged())
218 						this.onReconfigure(reconfigurationEntry);
219 				}
220 
221 				Thread.sleep(this.interval);
222 			} catch (InterruptedException e) {
223 				continue;
224 			} catch (Exception e) {
225 				String msg = "The ReconfigurationService had a problem";
226 				this.getLogger().error(msg, e);
227 				continue;
228 			}
229 		}
230 	}
231 
232 	/////////////////////////////////////////////////////////////////////////
233 	// Service implementation
234 	/////////////////////////////////////////////////////////////////////////
235 
236 	/**
237 	 * Reconfigure either the whole container or a list of services. This method is
238 	 * called within a seperate worker thred.
239 	 *
240 	 * @param reconfigurationEntry the configuration what to reconfigure
241 	 * @throws Exception the reconfiguration failed
242 	 */
243 	protected void onReconfigure(ReconfigurationEntry reconfigurationEntry) throws Exception {
244 		if (reconfigurationEntry.getServiceList() == null) {
245 			// reconfigure the whole container using Avalon Lifecycle Spec
246 			InputStream is = reconfigurationEntry.locate();
247 			DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
248 			Configuration configuration = builder.build(is);
249 			is.close();
250 			is = null;
251 
252 			this.getLogger().warn("Starting to reconfigure the container");
253 
254 			if (this.serviceManager instanceof Suspendable) {
255 				this.getLogger().info("Calling suspend() of the container");
256 				((Suspendable) this.serviceManager).suspend();
257 			}
258 
259 			if (this.serviceManager instanceof Reconfigurable) {
260 				this.getLogger().info("Calling reconfigure() of the container");
261 				((Reconfigurable) this.serviceManager).reconfigure(configuration);
262 			}
263 
264 			if (this.serviceManager instanceof Suspendable) {
265 				this.getLogger().info("Calling resume() of the container");
266 				((Suspendable) this.serviceManager).resume();
267 			}
268 
269 			this.getLogger().info("Reconfiguring the container was successful");
270 		} else {
271 			String[] serviceList = reconfigurationEntry.getServiceList();
272 			this.getLogger().warn("Calling reconfigure() on individual services : " + serviceList.length);
273 			this.serviceLifecycleManager.reconfigure(serviceList);
274 		}
275 	}
276 
277 	/**
278 	 * @return Returns the reconfigurationEntryList.
279 	 */
280 	private synchronized ReconfigurationEntry[] getReconfigurationEntryList() {
281 		return reconfigurationEntryList;
282 	}
283 
284 	/**
285 	 * @param reconfigurationEntryList The reconfigurationEntryList to set.
286 	 */
287 	private synchronized void setReconfigurationEntryList(ReconfigurationEntry[] reconfigurationEntryList) {
288 		this.reconfigurationEntryList = reconfigurationEntryList;
289 	}
290 }