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.model.meta;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.logging.Logger;
23  
24  import org.fourthline.cling.model.ServiceReference;
25  import org.fourthline.cling.model.ValidationError;
26  import org.fourthline.cling.model.ValidationException;
27  import org.fourthline.cling.model.types.Datatype;
28  import org.fourthline.cling.model.types.ServiceId;
29  import org.fourthline.cling.model.types.ServiceType;
30  
31  /**
32   * The metadata of a service, with actions and state variables.
33   *
34   * @author Christian Bauer
35   */
36  public abstract class Service<D extends Device, S extends Service> {
37  
38  	final private static Logger log = Logger.getLogger(Service.class.getName());
39  
40      final private ServiceType serviceType;
41      final private ServiceId serviceId;
42  
43  
44      final private Map<String, Action> actions = new HashMap<>();
45      final private Map<String, StateVariable> stateVariables = new HashMap<>();
46  
47      // Package mutable state
48      private D device;
49  
50      public Service(ServiceType serviceType, ServiceId serviceId) throws ValidationException {
51          this(serviceType, serviceId, null, null);
52      }
53  
54      public Service(ServiceType serviceType, ServiceId serviceId,
55                     Action<S>[] actions, StateVariable<S>[] stateVariables) throws ValidationException {
56  
57          this.serviceType = serviceType;
58          this.serviceId = serviceId;
59  
60          if (actions != null) {
61              for (Action action : actions) {
62                  this.actions.put(action.getName(), action);
63                  action.setService(this);
64              }
65          }
66  
67          if (stateVariables != null) {
68              for (StateVariable stateVariable : stateVariables) {
69                  this.stateVariables.put(stateVariable.getName(), stateVariable);
70                  stateVariable.setService(this);
71              }
72          }
73  
74      }
75  
76      public ServiceType getServiceType() {
77          return serviceType;
78      }
79  
80      public ServiceId getServiceId() {
81          return serviceId;
82      }
83  
84      public boolean hasActions() {
85          return getActions() != null && getActions().length > 0;
86      }
87  
88      public Action<S>[] getActions() {
89          return actions == null ? null : actions.values().toArray(new Action[actions.values().size()]);
90      }
91  
92      public boolean hasStateVariables() {
93          // TODO: Spec says always has to have at least one...
94          return getStateVariables() != null && getStateVariables().length > 0;
95      }
96  
97      public StateVariable<S>[] getStateVariables() {
98          return stateVariables == null ? null : stateVariables.values().toArray(new StateVariable[stateVariables.values().size()]);
99      }
100 
101     public D getDevice() {
102         return device;
103     }
104 
105     void setDevice(D device) {
106         if (this.device != null)
107             throw new IllegalStateException("Final value has been set already, model is immutable");
108         this.device = device;
109     }
110 
111     public Action<S> getAction(String name) {
112         return actions == null ? null : actions.get(name);
113     }
114 
115     public StateVariable<S> getStateVariable(String name) {
116         // Some magic necessary for the deprecated 'query state variable' action stuff
117         if (QueryStateVariableAction.VIRTUAL_STATEVARIABLE_INPUT.equals(name)) {
118             return new StateVariable(
119                     QueryStateVariableAction.VIRTUAL_STATEVARIABLE_INPUT,
120                     new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype())
121             );
122         }
123         if (QueryStateVariableAction.VIRTUAL_STATEVARIABLE_OUTPUT.equals(name)) {
124             return new StateVariable(
125                     QueryStateVariableAction.VIRTUAL_STATEVARIABLE_OUTPUT,
126                     new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype())
127             );
128         }
129         return stateVariables == null ? null : stateVariables.get(name);
130     }
131 
132     public StateVariable<S> getRelatedStateVariable(ActionArgument argument) {
133         return getStateVariable(argument.getRelatedStateVariableName());
134     }
135 
136     public Datatype<S> getDatatype(ActionArgument argument) {
137         return getRelatedStateVariable(argument).getTypeDetails().getDatatype();
138     }
139 
140     public ServiceReference getReference() {
141         return new ServiceReference(getDevice().getIdentity().getUdn(), getServiceId());
142     }
143 
144     public List<ValidationError> validate() {
145         List<ValidationError> errors = new ArrayList<>();
146 
147         if (getServiceType() == null) {
148             errors.add(new ValidationError(
149                     getClass(),
150                     "serviceType",
151                     "Service type/info is required"
152             ));
153         }
154 
155         if (getServiceId() == null) {
156             errors.add(new ValidationError(
157                     getClass(),
158                     "serviceId",
159                     "Service ID is required"
160             ));
161         }
162 
163         // TODO: If the service has no evented variables, it should not have an event subscription URL, which means
164         // the url element in the device descriptor must be present, but empty!!!!
165 
166         /* TODO: This doesn't fit into our meta model, we don't know if a service has state variables until
167          we completely hydrate it from a service descriptor
168         if (getStateVariables().length == 0) {
169             errors.add(new ValidationError(
170                     getClass(),
171                     "stateVariables",
172                     "Service must have at least one state variable"
173             ));
174         }
175         */
176         if (hasStateVariables()) {
177             for (StateVariable stateVariable : getStateVariables()) {
178                 errors.addAll(stateVariable.validate());
179             }
180         }
181 
182         if (hasActions()) {
183             for (Action action : getActions()) {
184 
185                 // Instead of bailing out here, we try to continue if an action is invalid
186                 // errors.addAll(action.validate());
187 
188                 List<ValidationError> actionErrors = action.validate();
189             	if(actionErrors.size() > 0) {
190                     actions.remove(action.getName()); // Remove it
191                     log.warning("Discarding invalid action of service '" + getServiceId() + "': " + action.getName());
192                     for (ValidationError actionError : actionErrors) {
193                         log.warning("Invalid action '" + action.getName() + "': " + actionError);
194                     }
195             	}
196             }
197         }
198 
199         return errors;
200     }
201 
202     public abstract Action getQueryStateVariableAction();
203 
204     @Override
205     public String toString() {
206         return "(" + getClass().getSimpleName() + ") ServiceId: " + getServiceId();
207     }
208 }