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    }