View Javadoc

1   /* This file is part of COPAL (COntext Provisioning for All).
2    *
3    * COPAL is a part of SM4All (Smart hoMes for All) project.
4    *
5    * COPAL is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU Lesser General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * COPAL is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public License
16   * along with COPAL. If not, see <http://www.gnu.org/licenses/>.
17   */
18  package at.ac.tuwien.infosys.sm4all.copal.api.helpers;
19  
20  import java.text.MessageFormat;
21  import java.util.Collection;
22  import java.util.HashMap;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  import java.util.concurrent.locks.ReadWriteLock;
28  import java.util.concurrent.locks.ReentrantReadWriteLock;
29  import org.apache.log4j.Logger;
30  import org.osgi.framework.BundleActivator;
31  import org.osgi.framework.BundleContext;
32  import at.ac.tuwien.infosys.sm4all.copal.api.ContextException;
33  import at.ac.tuwien.infosys.sm4all.copal.api.ContextListener;
34  import at.ac.tuwien.infosys.sm4all.copal.api.ContextQuery;
35  import at.ac.tuwien.infosys.sm4all.copal.api.ProcessedEventQuery;
36  import at.ac.tuwien.infosys.sm4all.copal.api.QueryDestroyedException;
37  import at.ac.tuwien.infosys.sm4all.copal.service.copal.ContextQueryFactory;
38  
39  /**
40   * Abstract helper class to be used as an OSGi Activator for bundles that need
41   * to register {@link ContextListener}s.
42   * 
43   * @author sanjin
44   */
45  public abstract class AbstractListenersActivator implements BundleActivator {
46  
47      private static final Logger LOGGER = Logger.getLogger(AbstractListenersActivator.class);
48  
49      private final Map<String, List<ContextListener>> listeners = new HashMap<String, List<ContextListener>>();
50      private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();
51      private final Map<String, ProcessedEventQuery> queries = new HashMap<String, ProcessedEventQuery>();
52      private final ReadWriteLock queriesLock = new ReentrantReadWriteLock();
53      private final QueryFactoryTracker tracker;
54      private final Context context;
55      private ContextQueryFactory factory;
56  
57      /**
58       * Creates instance of OSGi Activator for bundles that need to register
59       * {@link ContextListener}s.
60       */
61      public AbstractListenersActivator() {
62          super();
63          this.tracker = new QueryFactoryTracker(this);
64          this.context = new Context(getClass().getClassLoader(),
65                  "listeners.cfg.xml");
66  
67          for (final String queryName : this.context.getDefinedQueries())
68              this.listeners.put(queryName, new LinkedList<ContextListener>());
69  
70          if (this.context.getDefinedEventTypes().length > 0)
71              LOGGER.warn("Definitions of event types found in listeners.cfg.xml! They will not be registered.");
72      }
73  
74      /**
75       * Called when bundle is started.
76       */
77      protected void start() {
78      /* do nothing */
79      }
80  
81      /**
82       * Called when {@link ContextQueryFactory} becomes available.
83       * 
84       * @param queryFactory the {@link ContextQueryFactory}.
85       */
86      protected void start(final ContextQueryFactory queryFactory) {
87      /* do nothing */
88      }
89  
90      /**
91       * Called when {@link ContextQueryFactory} becomes unavailable.
92       */
93      protected void stop() {
94      /* do nothing */
95      }
96  
97      /**
98       * Get successfully created {@link ContextQuery} defined in the
99       * <code>context.cfg.xml</code> file that has specified name.
100      * 
101      * @param name the name of successfully created {@link ContextQuery}.
102      * @return the {@link ContextQuery} with specified name or <code>null</code>
103      *         if query was not successfully created or not defined in the
104      *         <code>context.cfg.xml</code> file.
105      */
106     public ContextQuery getQuery(final String name) {
107         ContextQuery result = null;
108 
109         this.queriesLock.readLock().lock();
110         try {
111             if (this.queries.containsKey(name))
112                 result = this.queries.get(name);
113         } finally {
114             this.queriesLock.readLock().unlock();
115         }
116 
117         return result;
118     }
119 
120     /**
121      * @return all successfully registered {@link ProcessedEventQuery}s defined
122      *         in the <code>context.cfg.xml</code> file.
123      */
124     public ProcessedEventQuery[] getCreatedQueries() {
125         final Collection<ProcessedEventQuery> result;
126 
127         this.queriesLock.readLock().lock();
128         try {
129             result = this.queries.values();
130         } finally {
131             this.queriesLock.readLock().unlock();
132         }
133 
134         return result.toArray(new ProcessedEventQuery[result.size()]);
135     }
136 
137     @Override
138     public final void start(final BundleContext bundleContext) {
139         this.tracker.start(bundleContext);
140 
141         start();
142     }
143 
144     @Override
145     public final void stop(final BundleContext bundleContext) {
146         setFactory(null);
147 
148         this.tracker.stop(bundleContext);
149     }
150 
151     /**
152      * Register specified {@link ContextListener} with {@link ContextQuery} with
153      * specified name that is defined in the <code>listeners.cfg.xml</code>.
154      * 
155      * @param queryName the name of the {@link ContextQuery} defined in the
156      *        <code>listeners.cfg.xml</code>.
157      * @param listener the {@link ContextListener} to register.
158      */
159     public final void register(final String queryName,
160             final ContextListener listener) {
161         this.listenersLock.writeLock().lock();
162         try {
163             if (this.listeners.containsKey(queryName)) {
164                 this.listeners.get(queryName).add(listener);
165 
166                 this.queriesLock.readLock().lock();
167                 try {
168                     if (this.queries.containsKey(queryName))
169                         try {
170                             this.queries.get(queryName).register(listener);
171                         } catch (final ContextException ex) {
172                             LOGGER.error(
173                                     MessageFormat.format(
174                                             "Could not register listener ''{0}'' with query ''{1}''!.",
175                                             listener.getName(), queryName), ex);
176                         }
177                 } finally {
178                     this.queriesLock.readLock().unlock();
179                 }
180             } else
181                 LOGGER.error(MessageFormat.format(
182                         "Query with name ''{0}'' is not defined in listeners.cfg.xml!",
183                         queryName));
184         } finally {
185             this.listenersLock.writeLock().unlock();
186         }
187     }
188 
189     /**
190      * Do not register specified {@link ContextListener} with
191      * {@link ContextQuery} with specified name that is defined in the
192      * <code>listeners.cfg.xml</code>
193      * 
194      * @param queryName the name of the {@link ContextQuery} defined in the
195      *        <code>listeners.cfg.xml</code>.
196      * @param listener the {@link ContextListener} to register.
197      */
198     public final void unregister(final String queryName,
199             final ContextListener listener) {
200         this.listenersLock.writeLock().lock();
201         try {
202             if (this.listeners.containsKey(queryName)) {
203                 this.queriesLock.readLock().lock();
204                 try {
205                     if (this.queries.containsKey(queryName)) {
206                         final String listenerName = listener.getName();
207                         try {
208                             this.queries.get(queryName).unregister(listenerName);
209                         } catch (final ContextException ex) {
210                             LOGGER.error(
211                                     MessageFormat.format(
212                                             "Could not unregister listener ''{0}'' from query ''{1}''!.",
213                                             listenerName, queryName), ex);
214                         }
215                     }
216                 } finally {
217                     this.queriesLock.readLock().unlock();
218                 }
219 
220                 this.listeners.get(queryName).remove(listener);
221             } else
222                 LOGGER.error(MessageFormat.format(
223                         "Query with name ''{0}'' is not defined in listeners.cfg.xml!",
224                         queryName));
225         } finally {
226             this.listenersLock.writeLock().unlock();
227         }
228     }
229 
230     /**
231      * Sets the {@link ContextQueryFactory}, destroys all previously created
232      * {@link ContextQuery}s and creates defined {@link ContextQuery}s and
233      * registers all {@link ContextListener}s if {@link ContextQueryFactory} is
234      * not <code>null</code> .
235      * 
236      * @param factory the {@link ContextQueryFactory}.
237      */
238     protected void setFactory(final ContextQueryFactory factory) {
239         this.listenersLock.readLock().lock();
240         this.queriesLock.writeLock().lock();
241         try {
242             if (this.factory != null) {
243                 stop();
244                 unregisterAll();
245                 destroyAll();
246             }
247             this.factory = factory;
248             if (this.factory != null) {
249                 final ProcessedEventQuery[] createdQueries = this.context.createQueries(factory);
250                 for (final ProcessedEventQuery query : createdQueries)
251                     this.queries.put(query.getName(), query);
252                 registerAll();
253                 start(factory);
254             }
255         } finally {
256             this.queriesLock.writeLock().unlock();
257             this.listenersLock.readLock().unlock();
258         }
259     }
260 
261     private void destroyAll() {
262         this.queriesLock.writeLock().lock();
263         try {
264             for (final ProcessedEventQuery query : this.queries.values()) {
265                 query.unregisterAll();
266                 try {
267                     query.destroy();
268                 } catch (final QueryDestroyedException ex) {
269                     LOGGER.warn(
270                             MessageFormat.format(
271                                     "Query ''{0}'' is unexpectedaly already destroyed! Ignoring.",
272                                     query.getName()), ex);
273                 }
274             }
275             this.queries.clear();
276         } finally {
277             this.queriesLock.writeLock().unlock();
278         }
279     }
280 
281     private void registerAll() {
282         this.listenersLock.readLock().lock();
283         this.queriesLock.readLock().lock();
284         try {
285             for (final Entry<String, List<ContextListener>> entry : this.listeners.entrySet()) {
286                 final String queryName = entry.getKey();
287                 if (this.queries.containsKey(queryName)) {
288                     final ProcessedEventQuery query = this.queries.get(queryName);
289                     for (final ContextListener listener : entry.getValue())
290                         try {
291                             query.register(listener);
292                         } catch (final ContextException ex) {
293                             LOGGER.error(
294                                     MessageFormat.format(
295                                             "Could not register listener ''{0}'' with query ''{1}''!.",
296                                             listener.getName(), queryName), ex);
297                         }
298                 }
299             }
300         } finally {
301             this.queriesLock.readLock().unlock();
302             this.listenersLock.readLock().unlock();
303         }
304     }
305 
306     private void unregisterAll() {
307         this.listenersLock.readLock().lock();
308         this.queriesLock.readLock().lock();
309         try {
310             for (final Entry<String, List<ContextListener>> entry : this.listeners.entrySet()) {
311                 final String queryName = entry.getKey();
312                 if (this.queries.containsKey(queryName)) {
313                     final ProcessedEventQuery query = this.queries.get(queryName);
314                     for (final ContextListener listener : entry.getValue()) {
315                         final String listenerName = listener.getName();
316                         try {
317                             query.unregister(listenerName);
318                         } catch (final ContextException ex) {
319                             LOGGER.error(
320                                     MessageFormat.format(
321                                             "Could not unregister listener ''{0}'' from query ''{1}''!.",
322                                             listenerName, queryName), ex);
323                         }
324                     }
325                 }
326             }
327         } finally {
328             this.queriesLock.readLock().unlock();
329             this.listenersLock.readLock().unlock();
330         }
331     }
332 
333     private static class QueryFactoryTracker extends AbstractGenericActivator {
334 
335         private final AbstractListenersActivator activator;
336 
337         public QueryFactoryTracker(final AbstractListenersActivator activator) {
338             super(ContextQueryFactory.class.getName());
339             this.activator = activator;
340         }
341 
342         @Override
343         protected void start() {
344             final ContextQueryFactory factory = getDependency(ContextQueryFactory.class.getName());
345             this.activator.setFactory(factory);
346         }
347 
348         @Override
349         protected void stop() {
350             this.activator.setFactory(null);
351         }
352     }
353 }