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    }