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.listener;
19  
20  import java.text.MessageFormat;
21  import java.util.HashMap;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.concurrent.locks.ReadWriteLock;
26  import java.util.concurrent.locks.ReentrantReadWriteLock;
27  import org.apache.log4j.Level;
28  import org.apache.log4j.Logger;
29  import org.osgi.framework.BundleActivator;
30  import org.osgi.framework.BundleContext;
31  import at.ac.tuwien.infosys.sm4all.copal.api.ContextException;
32  import at.ac.tuwien.infosys.sm4all.copal.api.conf.QueriesConfiguration;
33  import at.ac.tuwien.infosys.sm4all.copal.api.osgi.Tracker;
34  import at.ac.tuwien.infosys.sm4all.copal.api.query.ContextQuery;
35  import at.ac.tuwien.infosys.sm4all.copal.api.query.ProcessedEventQuery;
36  import at.ac.tuwien.infosys.sm4all.copal.api.query.QueryObserver;
37  import at.ac.tuwien.infosys.sm4all.copal.api.query.QueryState;
38  import at.ac.tuwien.infosys.sm4all.copal.api.service.ContextQueryFactory;
39  import at.ac.tuwien.infosys.sm4all.copal.api.util.NotRegisteredException;
40  
41  /**
42   * Abstract helper class to be used as an OSGi {@link BundleActivator} for
43   * bundles that need to create {@link ContextQuery}s and register
44   * {@link ContextListener}s.
45   * 
46   * @author sanjin
47   */
48  public class ListenersActivator implements BundleActivator, QueryObserver {
49  
50      /**
51       * The name of configuration file that defines {@link ContextQuery}s used to
52       * register {@link ContextListener}s.
53       */
54      public static final String CONFIGURATION_FILE = "listeners.cfg.xml";
55  
56      private static final Logger LOGGER = Logger.getLogger(ListenersActivator.class);
57  
58      private final ReadWriteLock lock = new ReentrantReadWriteLock();
59      private final Map<String, List<ContextListener>> listeners = new HashMap<String, List<ContextListener>>();
60      private final Tracker<ContextQueryFactory> tracker;
61      private final QueriesConfiguration configuration;
62      private ContextQueryFactory factory;
63  
64      {
65          this.tracker = new Tracker<ContextQueryFactory>(
66                  ContextQueryFactory.class.getName()) {
67  
68              @SuppressWarnings("hiding")
69              @Override
70              protected void start(final ContextQueryFactory factory) {
71                  setFactory(factory);
72              }
73  
74              @SuppressWarnings("hiding")
75              @Override
76              protected void stop(final ContextQueryFactory factory) {
77                  setFactory(null);
78              }
79          };
80      }
81  
82      /**
83       * Creates instance of OSGi {@link BundleActivator} for bundles that to
84       * create {@link ContextQuery}s and register {@link ContextListener}s.
85       */
86      protected ListenersActivator() {
87          super();
88  
89          this.configuration = new QueriesConfiguration(
90                  getClass().getClassLoader(), CONFIGURATION_FILE);
91  
92          for (final String queryName : this.configuration.getDefined()) {
93              this.listeners.put(queryName, new LinkedList<ContextListener>());
94          }
95      }
96  
97      /**
98       * Called when bundle is started.
99       */
100     protected void start() {
101         /* do nothing */
102     }
103 
104     /**
105      * Called when {@link ContextQueryFactory} becomes available.
106      * 
107      * @param queryFactory the {@link ContextQueryFactory}.
108      */
109     protected void start(final ContextQueryFactory queryFactory) {
110         /* do nothing */
111     }
112 
113     /**
114      * Called when {@link ContextQueryFactory} becomes unavailable.
115      */
116     protected void stop() {
117         /* do nothing */
118     }
119 
120     /**
121      * Returns created {@link ProcessedEventQuery} that has specified name
122      * defined in the {@link #CONFIGURATION_FILE}. If
123      * {@link ProcessedEventQuery} with specified name is not defined in the
124      * {@link #CONFIGURATION_FILE} or the {@link ProcessedEventQuery} was not
125      * successfully created, the <code>null</code> value is returned.
126      * 
127      * @param name the name of created {@link ProcessedEventQuery}.
128      * @return the created {@link ProcessedEventQuery}.
129      */
130     public ContextQuery getQuery(final String name) {
131         ContextQuery result = null;
132 
133         for (final ContextQuery query : this.configuration.getCreated()) {
134             if (name.equals(query.getName())) {
135                 result = query;
136                 break;
137             }
138         }
139 
140         return result;
141     }
142 
143     /**
144      * Returns all created {@link ProcessedEventQuery}s defined in the
145      * {@link #CONFIGURATION_FILE}.
146      * 
147      * @return all created {@link ProcessedEventQuery}s.
148      */
149     public ProcessedEventQuery[] getRegisteredQueries() {
150         return this.configuration.getCreated();
151     }
152 
153     @Override
154     public final void start(final BundleContext bundleContext) {
155         this.configuration.start(bundleContext);
156         this.tracker.start(bundleContext);
157 
158         start();
159     }
160 
161     @Override
162     public final void stop(final BundleContext bundleContext) {
163         setFactory(null);
164 
165         this.tracker.stop(bundleContext);
166         this.configuration.stop(bundleContext);
167     }
168 
169     /**
170      * Register specified {@link ContextListener} with a {@link ContextQuery}
171      * that has specified name.
172      * 
173      * @param queryName the name of the {@link ContextQuery}.
174      * @param listener the {@link ContextListener}.
175      */
176     public final void register(final String queryName,
177             final ContextListener listener) {
178         this.lock.writeLock().lock();
179         try {
180             if (!this.listeners.containsKey(queryName)) {
181                 this.listeners.put(queryName, new LinkedList<ContextListener>());
182             }
183             this.listeners.get(queryName).add(listener);
184 
185             if (null != this.factory) {
186                 final ContextQuery query = this.factory.getQuery(queryName);
187                 if ((null != query) && (query instanceof ProcessedEventQuery)) {
188                     try {
189                         ((ProcessedEventQuery) query).register(listener);
190                     } catch (final ContextException ex) {
191                         if (LOGGER.isEnabledFor(Level.ERROR)) {
192                             LOGGER.error(
193                                     MessageFormat.format(
194                                             "Could not register listener ''{0}'' with query ''{1}''!.",
195                                             listener.getName(), queryName), ex);
196                         }
197                     }
198                 }
199             }
200         } finally {
201             this.lock.writeLock().unlock();
202         }
203     }
204 
205     /**
206      * Do not register specified {@link ContextListener} with
207      * {@link ContextQuery} that has specified name.
208      * 
209      * @param queryName the name of the {@link ContextQuery}.
210      * @param listener the {@link ContextListener}.
211      */
212     public final void unregister(final String queryName,
213             final ContextListener listener) {
214         final String listenerName = listener.getName();
215 
216         this.lock.writeLock().lock();
217         try {
218             if (this.listeners.containsKey(queryName)
219                     && this.listeners.get(queryName).remove(listener)) {
220                 if (null != this.factory) {
221                     final ContextQuery query = this.factory.getQuery(queryName);
222                     if ((null != query)
223                             && (query instanceof ProcessedEventQuery)) {
224                         try {
225                             ((ProcessedEventQuery) query).unregister(listenerName);
226                         } catch (final NotRegisteredException ex) {
227                             if (LOGGER.isEnabledFor(Level.ERROR)) {
228                                 LOGGER.error(
229                                         MessageFormat.format(
230                                                 "Could not unregister listener ''{0}'' from query ''{1}''!.",
231                                                 listenerName, queryName), ex);
232                             }
233                         }
234                     }
235                 }
236             } else {
237                 if (LOGGER.isEnabledFor(Level.ERROR)) {
238                     LOGGER.error(MessageFormat.format(
239                             "Listener ''{0}'' was not registered with query ''{1}''!",
240                             listenerName, queryName));
241                 }
242             }
243         } finally {
244             this.lock.writeLock().unlock();
245         }
246     }
247 
248     /**
249      * Sets the {@link ContextQueryFactory}, destroys all previously created
250      * {@link ContextQuery}s and creates defined {@link ContextQuery}s and
251      * registers all {@link ContextListener}s if {@link ContextQueryFactory} is
252      * not <code>null</code>.
253      * 
254      * @param factory the {@link ContextQueryFactory}.
255      */
256     protected void setFactory(final ContextQueryFactory factory) {
257         this.lock.writeLock().lock();
258         try {
259             if (null != this.factory) {
260                 this.factory.detach(this);
261                 stop();
262             }
263             this.factory = factory;
264             if (null != this.factory) {
265                 this.factory.attach(this);
266                 start(factory);
267             }
268         } finally {
269             this.lock.writeLock().unlock();
270         }
271     }
272 
273     @Override
274     public void update(final QueryState state, final ContextQuery query) {
275         if (query instanceof ProcessedEventQuery) {
276             switch (state) {
277             case Created:
278                 registerAll((ProcessedEventQuery) query);
279                 break;
280             case Destroyed:
281                 unregisterAll((ProcessedEventQuery) query);
282                 break;
283             default:
284                 throw new UnsupportedOperationException(MessageFormat.format(
285                         "Unknown query state: {0}", state));
286             }
287         }
288     }
289 
290     private void registerAll(final ProcessedEventQuery query) {
291         final String queryName = query.getName();
292 
293         this.lock.readLock().lock();
294         try {
295             if (this.listeners.containsKey(queryName)) {
296                 for (final ContextListener listener : this.listeners.get(queryName)) {
297                     try {
298                         query.register(listener);
299                     } catch (final ContextException ex) {
300                         if (LOGGER.isEnabledFor(Level.ERROR)) {
301                             LOGGER.error(
302                                     MessageFormat.format(
303                                             "Could not register listener ''{0}'' with query ''{1}''!.",
304                                             listener.getName(), queryName), ex);
305                         }
306                     }
307                 }
308             }
309         } finally {
310             this.lock.readLock().unlock();
311         }
312     }
313 
314     private void unregisterAll(final ProcessedEventQuery query) {
315         final String queryName = query.getName();
316 
317         this.lock.readLock().lock();
318         try {
319             if (this.listeners.containsKey(queryName)) {
320                 for (final ContextListener listener : this.listeners.get(queryName)) {
321                     final String listenerName = listener.getName();
322                     try {
323                         query.unregister(listenerName);
324                     } catch (final NotRegisteredException ex) {
325                         if (LOGGER.isEnabledFor(Level.ERROR)) {
326                             LOGGER.error(
327                                     MessageFormat.format(
328                                             "Could not unregister listener ''{0}'' from query ''{1}''!.",
329                                             listenerName, queryName), ex);
330                         }
331                     }
332                 }
333             }
334         } finally {
335             this.lock.readLock().unlock();
336         }
337     }
338 }