View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.support.lastchange;
17  
18  import org.fourthline.cling.model.DefaultServiceManager;
19  import org.fourthline.cling.model.meta.LocalService;
20  import org.fourthline.cling.model.meta.StateVariable;
21  import org.fourthline.cling.model.state.StateVariableValue;
22  import org.fourthline.cling.model.types.UnsignedIntegerFourBytes;
23  
24  import java.util.ArrayList;
25  import java.util.Collection;
26  
27  /**
28   * Handles the "initial" event state for GENA subscriptions to services using LastChange.
29   * <p>
30   * When a GENA subscription is made on your AVTransport/RenderingControl service, you have to
31   * read the initial state of the service. Ususally Cling would do this for you and simply
32   * access all the state variables of your service behind the scenes. But the
33   * AVTransport/RenderingControl service doesn't use regular UPnP state variable eventing
34   * internally, they rely on the awful "LastChange" mechanism for their "logical" instances.
35   * </p>
36   * <p>
37   * Use this {@link org.fourthline.cling.model.ServiceManager} instead of the default one for
38   * these services.
39   * </p>
40   *
41   * @author Christian Bauer
42   */
43  public class LastChangeAwareServiceManager<T extends LastChangeDelegator> extends DefaultServiceManager<T> {
44  
45      final protected LastChangeParser lastChangeParser;
46  
47      public LastChangeAwareServiceManager(LocalService<T> localService,
48                                           LastChangeParser lastChangeParser) {
49          this(localService, null, lastChangeParser);
50      }
51  
52      public LastChangeAwareServiceManager(LocalService<T> localService,
53                                           Class<T> serviceClass,
54                                           LastChangeParser lastChangeParser) {
55          super(localService, serviceClass);
56          this.lastChangeParser = lastChangeParser;
57      }
58  
59      protected LastChangeParser getLastChangeParser() {
60          return lastChangeParser;
61      }
62  
63      /**
64       * Call this method to propagate all accumulated "LastChange" values to GENA subscribers.
65       */
66      public void fireLastChange() {
67  
68          // We need to obtain locks in the right order to avoid deadlocks:
69          // 1. The lock() of the DefaultServiceManager
70          // 2. The monitor/synchronized of the LastChange.fire() method
71  
72      	lock();
73      	try {
74              getImplementation().getLastChange().fire(getPropertyChangeSupport());
75      	} finally {
76      		unlock();
77      	}
78      }
79  
80      @Override
81      protected Collection<StateVariableValue> readInitialEventedStateVariableValues() throws Exception {
82  
83          // We don't use the service's internal LastChange but a fresh new one just for
84          // this initial event. Modifying the internal one would trigger event notification's
85          // to other subscribers!
86          LastChange lc = new LastChange(getLastChangeParser());
87  
88          // Get the current "logical" instances of the service
89          UnsignedIntegerFourBytes[] ids = getImplementation().getCurrentInstanceIds();
90          if (ids.length > 0) {
91              for (UnsignedIntegerFourBytes instanceId : ids) {
92                  // Iterate through all "logical" instances and ask them what their state is
93                  getImplementation().appendCurrentState(lc, instanceId);
94              }
95          } else {
96              // Use the default "logical" instance with ID 0
97              getImplementation().appendCurrentState(lc, new UnsignedIntegerFourBytes(0));
98          }
99  
100         // Sum it all up and return it in the initial event to the GENA subscriber
101         StateVariable variable = getService().getStateVariable("LastChange");
102         Collection<StateVariableValue> values = new ArrayList<>();
103         values.add(new StateVariableValue(variable, lc.toString()));
104         return values;
105     }
106 
107 }