1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.model;
17
18 import java.beans.PropertyChangeEvent;
19 import java.beans.PropertyChangeListener;
20 import java.beans.PropertyChangeSupport;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.locks.ReentrantLock;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import org.fourthline.cling.model.meta.LocalService;
31 import org.fourthline.cling.model.meta.StateVariable;
32 import org.fourthline.cling.model.state.StateVariableAccessor;
33 import org.fourthline.cling.model.state.StateVariableValue;
34 import org.seamless.util.Exceptions;
35 import org.seamless.util.Reflections;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 public class DefaultServiceManager<T> implements ServiceManager<T> {
52
53 private static Logger log = Logger.getLogger(DefaultServiceManager.class.getName());
54
55 final protected LocalService<T> service;
56 final protected Class<T> serviceClass;
57 final protected ReentrantLock lock = new ReentrantLock(true);
58
59
60 protected T serviceImpl;
61 protected PropertyChangeSupport propertyChangeSupport;
62
63 protected DefaultServiceManager(LocalService<T> service) {
64 this(service, null);
65 }
66
67 public DefaultServiceManager(LocalService<T> service, Class<T> serviceClass) {
68 this.service = service;
69 this.serviceClass = serviceClass;
70 }
71
72
73
74 protected void lock() {
75 try {
76 if (lock.tryLock(getLockTimeoutMillis(), TimeUnit.MILLISECONDS)) {
77 if (log.isLoggable(Level.FINEST))
78 log.finest("Acquired lock");
79 } else {
80 throw new RuntimeException("Failed to acquire lock in milliseconds: " + getLockTimeoutMillis());
81 }
82 } catch (InterruptedException e) {
83 throw new RuntimeException("Failed to acquire lock:" + e);
84 }
85 }
86
87 protected void unlock() {
88 if (log.isLoggable(Level.FINEST))
89 log.finest("Releasing lock");
90 lock.unlock();
91 }
92
93 protected int getLockTimeoutMillis() {
94 return 500;
95 }
96
97 public LocalService<T> getService() {
98 return service;
99 }
100
101 public T getImplementation() {
102 lock();
103 try {
104 if (serviceImpl == null) {
105 init();
106 }
107 return serviceImpl;
108 } finally {
109 unlock();
110 }
111 }
112
113 public PropertyChangeSupport getPropertyChangeSupport() {
114 lock();
115 try {
116 if (propertyChangeSupport == null) {
117 init();
118 }
119 return propertyChangeSupport;
120 } finally {
121 unlock();
122 }
123 }
124
125 public void execute(Command<T> cmd) throws Exception {
126 lock();
127 try {
128 cmd.execute(this);
129 } finally {
130 unlock();
131 }
132 }
133
134 @Override
135 public Collection<StateVariableValue> getCurrentState() throws Exception {
136 lock();
137 try {
138 Collection<StateVariableValue> values = readInitialEventedStateVariableValues();
139 if (values != null) {
140 log.fine("Obtained initial state variable values for event, skipping individual state variable accessors");
141 return values;
142 }
143 values = new ArrayList<>();
144 for (StateVariable stateVariable : getService().getStateVariables()) {
145 if (stateVariable.getEventDetails().isSendEvents()) {
146 StateVariableAccessor accessor = getService().getAccessor(stateVariable);
147 if (accessor == null)
148 throw new IllegalStateException("No accessor for evented state variable");
149 values.add(accessor.read(stateVariable, getImplementation()));
150 }
151 }
152 return values;
153 } finally {
154 unlock();
155 }
156 }
157
158 protected Collection<StateVariableValue> getCurrentState(String[] variableNames) throws Exception {
159 lock();
160 try {
161 Collection<StateVariableValue> values = new ArrayList<>();
162 for (String variableName : variableNames) {
163 variableName = variableName.trim();
164
165 StateVariable stateVariable = getService().getStateVariable(variableName);
166 if (stateVariable == null || !stateVariable.getEventDetails().isSendEvents()) {
167 log.fine("Ignoring unknown or non-evented state variable: " + variableName);
168 continue;
169 }
170
171 StateVariableAccessor accessor = getService().getAccessor(stateVariable);
172 if (accessor == null) {
173 log.warning("Ignoring evented state variable without accessor: " + variableName);
174 continue;
175 }
176 values.add(accessor.read(stateVariable, getImplementation()));
177 }
178 return values;
179 } finally {
180 unlock();
181 }
182 }
183
184 protected void init() {
185 log.fine("No service implementation instance available, initializing...");
186 try {
187
188 serviceImpl = createServiceInstance();
189
190
191 propertyChangeSupport = createPropertyChangeSupport(serviceImpl);
192 propertyChangeSupport.addPropertyChangeListener(createPropertyChangeListener(serviceImpl));
193
194 } catch (Exception ex) {
195 throw new RuntimeException("Could not initialize implementation: " + ex, ex);
196 }
197 }
198
199 protected T createServiceInstance() throws Exception {
200 if (serviceClass == null) {
201 throw new IllegalStateException("Subclass has to provide service class or override createServiceInstance()");
202 }
203 try {
204
205 return serviceClass.getConstructor(LocalService.class).newInstance(getService());
206 } catch (NoSuchMethodException ex) {
207 log.fine("Creating new service implementation instance with no-arg constructor: " + serviceClass.getName());
208 return serviceClass.newInstance();
209 }
210 }
211
212 protected PropertyChangeSupport createPropertyChangeSupport(T serviceImpl) throws Exception {
213 Method m;
214 if ((m = Reflections.getGetterMethod(serviceImpl.getClass(), "propertyChangeSupport")) != null &&
215 PropertyChangeSupport.class.isAssignableFrom(m.getReturnType())) {
216 log.fine("Service implementation instance offers PropertyChangeSupport, using that: " + serviceImpl.getClass().getName());
217 return (PropertyChangeSupport) m.invoke(serviceImpl);
218 }
219 log.fine("Creating new PropertyChangeSupport for service implementation: " + serviceImpl.getClass().getName());
220 return new PropertyChangeSupport(serviceImpl);
221 }
222
223 protected PropertyChangeListener createPropertyChangeListener(T serviceImpl) throws Exception {
224 return new DefaultPropertyChangeListener();
225 }
226
227 protected Collection<StateVariableValue> readInitialEventedStateVariableValues() throws Exception {
228 return null;
229 }
230
231 @Override
232 public String toString() {
233 return "(" + getClass().getSimpleName() + ") Implementation: " + serviceImpl;
234 }
235
236 protected class DefaultPropertyChangeListener implements PropertyChangeListener {
237
238 public void propertyChange(PropertyChangeEvent e) {
239 log.finer("Property change event on local service: " + e.getPropertyName());
240
241
242 if (e.getPropertyName().equals(EVENTED_STATE_VARIABLES)) return;
243
244 String[] variableNames = ModelUtil.fromCommaSeparatedList(e.getPropertyName());
245 log.fine("Changed variable names: " + Arrays.toString(variableNames));
246
247 try {
248 Collection<StateVariableValue> currentValues = getCurrentState(variableNames);
249
250 if (!currentValues.isEmpty()) {
251 getPropertyChangeSupport().firePropertyChange(
252 EVENTED_STATE_VARIABLES,
253 null,
254 currentValues
255 );
256 }
257
258 } catch (Exception ex) {
259
260 log.log(
261 Level.SEVERE,
262 "Error reading state of service after state variable update event: " + Exceptions.unwrap(ex),
263 ex
264 );
265 }
266 }
267 }
268 }