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 }