001 /*
002 * Copyright 2005-2007 The Kuali Foundation
003 *
004 *
005 * Licensed under the Educational Community License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.opensource.org/licenses/ecl2.php
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.kuali.rice.ksb.messaging;
018
019 import java.util.List;
020 import java.util.Map;
021
022 import javax.ws.rs.Path;
023
024 import org.apache.commons.collections.BidiMap;
025 import org.apache.commons.collections.bidimap.DualHashBidiMap;
026 import org.kuali.rice.core.config.ConfigurationException;
027 import org.kuali.rice.core.exception.RiceRuntimeException;
028
029 /**
030 * Service definition for RESTful services. A JAX-WS service has a resource class, which is the class or
031 * interface marked by the JAX-WS annotations (e.g. @Path, @GET, etc). This may or may not be the implementation
032 * class.
033 * @author Kuali Rice Team (rice.collab@kuali.org)
034 */
035 public class RESTServiceDefinition extends ServiceDefinition {
036
037 private static final long serialVersionUID = 5892163789061959602L;
038
039 private String resourceClass;
040 transient private List<Object> resources;
041 private BidiMap resourceToClassNameMap;
042 transient private List<Object> providers;
043 transient private Map<Object, Object> extensionMappings;
044 transient private Map<Object, Object> languageMappings;
045
046 /**
047 * Default constructor. Sets bus security to FALSE.
048 */
049 public RESTServiceDefinition() {
050 super(Boolean.FALSE);
051 }
052
053 /**
054 * To ensure transparency that RESTful services are not digitally signed, throw an exception
055 * if someone tries to enable bus security.
056 *
057 * @see org.kuali.rice.ksb.messaging.ServiceDefinition#setBusSecurity(java.lang.Boolean)
058 */
059 @Override
060 public void setBusSecurity(Boolean busSecurity) {
061 if (busSecurity == true) {
062 throw new RiceRuntimeException("Rice does not support bus security (digital request/response signing) " +
063 "for RESTful services");
064 }
065 super.setBusSecurity(busSecurity);
066 }
067
068 /**
069 * Set the resourceClass, the class or interface marked by the JAX-WS annotations
070 * which specify the RESTful URL interface.
071 * @param resourceClass the resourceClass to set
072 */
073 public void setResourceClass(String resourceClass) {
074 this.resourceClass = resourceClass;
075 }
076
077 /**
078 * @see #setResourceClass(String)
079 * @return the resourceClass
080 */
081 public String getResourceClass() {
082 return this.resourceClass;
083 }
084
085 /**
086 * sets the service implementation
087 *
088 * @see org.kuali.rice.ksb.messaging.ServiceDefinition#setService(java.lang.Object)
089 */
090 @Override
091 public void setService(Object service) {
092 super.setService(service);
093 }
094
095 /**
096 * does some simple validation of this RESTServiceDefinition
097 *
098 * @see org.kuali.rice.ksb.messaging.ServiceDefinition#validate()
099 */
100 @Override
101 public void validate() {
102
103 List<Object> resources = getResources();
104
105 if (resources != null && !resources.isEmpty()) {
106 resourceToClassNameMap = new DualHashBidiMap();
107 for (Object resource : resources) {
108 // If there is no service set then we have to assume that it's the first resource
109 if (getService() == null) {
110 setService(resource);
111 }
112
113 Class resourceClass = resource.getClass();
114 if (resourceClass != null) {
115 Class[] interfaces = null;
116
117 if (resourceClass.isInterface()) {
118 interfaces = new Class[1];
119 interfaces[0] = resourceClass;
120 } else {
121 interfaces = resourceClass.getInterfaces();
122 }
123
124 if (interfaces != null) {
125 for (Class iface : interfaces) {
126 Path pathAnnotation = (Path)iface.getAnnotation(Path.class);
127 if (pathAnnotation != null) {
128 String pathAnnotationValue = pathAnnotation.value();
129 String resourceId = pathAnnotationValue == null || pathAnnotationValue.equals("/") ? iface.getSimpleName() : pathAnnotationValue;
130 resourceToClassNameMap.put(resourceId, iface.getName());
131 } else {
132 // If no path annotation exists, use the simple class name
133 resourceToClassNameMap.put(iface.getSimpleName(), iface.getName());
134 }
135 }
136 }
137 }
138 }
139
140 }
141
142 super.validate();
143
144 // if interface is null, set it to the service class
145 if (getResourceClass() == null) {
146 Class[] interfaces = getService().getClass().getInterfaces();
147 if (interfaces != null && interfaces.length > 0) {
148 setResourceClass(interfaces[0].getName());
149 } else {
150 throw new ConfigurationException("resource class must be set to export a REST service");
151 }
152 }
153
154 // Validate that the JAX-WS annotated class / interface is available to the classloader.
155 try {
156 Class.forName(getResourceClass());
157 } catch (ClassNotFoundException e) {
158 throw new ConfigurationException(
159 "resource class '" + getResourceClass() + "' could not be found in the classpath");
160 }
161
162 if (getBusSecurity() == null) {
163 setBusSecurity(false);
164 }
165 }
166
167 /**
168 * @return true if the given {@link RESTServiceDefinition} has the same resource class as this one.
169 * @see org.kuali.rice.ksb.messaging.ServiceDefinition#isSame(org.kuali.rice.ksb.messaging.ServiceDefinition)
170 */
171 @Override
172 public boolean isSame(final ServiceDefinition serviceDefinition) {
173 boolean same = super.isSame(serviceDefinition)
174 && serviceDefinition instanceof RESTServiceDefinition;
175 if (!same) {
176 return same;
177 }
178
179 RESTServiceDefinition otherServiceDefinition = (RESTServiceDefinition) serviceDefinition;
180
181 // To be the same, they have to have the same resource class name
182 if (!otherServiceDefinition.getResourceClass().equals(this.getResourceClass()))
183 return false;
184
185 // If neither has multiple resources, then they are the same
186 if (otherServiceDefinition.getResourceToClassNameMap() == null && getResourceToClassNameMap() == null)
187 return true;
188
189 // If one of them has multiple resources and the other doesn't, then they're not the same
190 if ((otherServiceDefinition.getResourceToClassNameMap() == null &&
191 getResourceToClassNameMap() != null)
192 ||
193 (otherServiceDefinition.getResourceToClassNameMap() != null &&
194 getResourceToClassNameMap() == null))
195 return false;
196
197 return otherServiceDefinition.getResourceToClassNameMap().equals(getResourceToClassNameMap());
198 }
199
200 /**
201 * @return the resources
202 */
203 public List<Object> getResources() {
204 return this.resources;
205 }
206
207 /**
208 * @param resources the resources to set
209 */
210 public void setResources(List<Object> resources) {
211 this.resources = resources;
212 }
213
214 /**
215 * @return the resourceToClassNameMap
216 */
217 @SuppressWarnings("unchecked")
218 public Map<String, String> getResourceToClassNameMap() {
219 return this.resourceToClassNameMap;
220 }
221
222 /**
223 * @param className
224 * @return true if this service contains a resource for the given class name
225 */
226 public boolean hasClass(String className) {
227 if (resourceToClassNameMap == null) return false;
228 return resourceToClassNameMap.containsValue(className);
229 }
230
231 /**
232 * @return the providers
233 */
234 public List<Object> getProviders() {
235 return this.providers;
236 }
237
238 /**
239 * @param providers the providers to set
240 */
241 public void setProviders(List<Object> providers) {
242 this.providers = providers;
243 }
244
245 /**
246 * @return the extensionMappings
247 */
248 public Map<Object, Object> getExtensionMappings() {
249 return this.extensionMappings;
250 }
251
252 /**
253 * @param extensionMappings the extensionMappings to set
254 */
255 public void setExtensionMappings(Map<Object, Object> extensionMappings) {
256 this.extensionMappings = extensionMappings;
257 }
258
259 /**
260 * @return the languageMappings
261 */
262 public Map<Object, Object> getLanguageMappings() {
263 return this.languageMappings;
264 }
265
266 /**
267 * @param languageMappings the languageMappings to set
268 */
269 public void setLanguageMappings(Map<Object, Object> languageMappings) {
270 this.languageMappings = languageMappings;
271 }
272
273
274
275 }