001/* 002 * Copyright 2011-2013 UnboundID Corp. 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim.wink; 019 020import com.unboundid.scim.data.AuthenticationScheme; 021import com.unboundid.scim.data.BulkConfig; 022import com.unboundid.scim.data.ChangePasswordConfig; 023import com.unboundid.scim.data.ETagConfig; 024import com.unboundid.scim.data.FilterConfig; 025import com.unboundid.scim.data.PatchConfig; 026import com.unboundid.scim.data.ServiceProviderConfig; 027import com.unboundid.scim.data.SortConfig; 028import com.unboundid.scim.data.XmlDataFormatConfig; 029import com.unboundid.scim.sdk.OAuthTokenHandler; 030import com.unboundid.scim.schema.CoreSchema; 031import com.unboundid.scim.sdk.SCIMBackend; 032import com.unboundid.scim.sdk.SCIMException; 033import com.unboundid.scim.sdk.SCIMObject; 034import org.apache.wink.common.WinkApplication; 035 036import java.io.File; 037import java.util.ArrayList; 038import java.util.Collection; 039import java.util.Collections; 040import java.util.HashMap; 041import java.util.HashSet; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045 046import static com.unboundid.scim.sdk.SCIMConstants.SCHEMA_URI_CORE; 047 048 049/** 050 * This class is a JAX-RS Application that returns the SCIM resource 051 * implementations. 052 */ 053public class SCIMApplication extends WinkApplication 054{ 055 private final Set<Object> instances; 056 private final Map<String,ResourceStats> resourceStats; 057 private final SCIMBackend backend; 058 private final boolean supportsOAuth; 059 private volatile long bulkMaxOperations = Long.MAX_VALUE; 060 private volatile long bulkMaxPayloadSize = Long.MAX_VALUE; 061 private volatile File tmpDataDir = null; 062 private AdjustableSemaphore bulkMaxConcurrentRequestsSemaphore = 063 new AdjustableSemaphore(Integer.MAX_VALUE); 064 065 066 /** 067 * Create a new SCIMApplication that defines the endpoints provided by the 068 * ResourceDescriptors and uses the provided backend to process the request. 069 * 070 * @param backend The backend that should be used to process the requests. 071 * @param tokenHandler The OAuthTokenHandler implementation to use (this may 072 * be {@code null}. 073 */ 074 public SCIMApplication( 075 final SCIMBackend backend, 076 final OAuthTokenHandler tokenHandler) 077 { 078 instances = new HashSet<Object>(); 079 080 instances.add(new RootResource(this)); 081 instances.add(new MonitorResource(this)); 082 083 instances.add(new ServiceProviderConfigResource(this)); 084 instances.add(new XMLServiceProviderConfigResource(this)); 085 instances.add(new JSONServiceProviderConfigResource(this)); 086 087 // The Bulk operation endpoint. 088 instances.add(new BulkResource(this, tokenHandler)); 089 instances.add(new JSONBulkResource(this, tokenHandler)); 090 instances.add(new XMLBulkResource(this, tokenHandler)); 091 092 instances.add(new SCIMResource(this, tokenHandler)); 093 instances.add(new XMLQueryResource(this, tokenHandler)); 094 instances.add(new JSONQueryResource(this, tokenHandler)); 095 096 this.resourceStats = new HashMap<String, ResourceStats>(); 097 this.backend = backend; 098 099 if (tokenHandler != null) 100 { 101 supportsOAuth = true; 102 } 103 else 104 { 105 supportsOAuth = false; 106 } 107 } 108 109 110 111 @Override 112 public Set<Object> getInstances() 113 { 114 return instances; 115 } 116 117 118 /** 119 * Retrieves the statistics for the resources being served by this 120 * application instance. 121 * 122 * @return The statistics for the resources being served by this 123 * application instance. 124 */ 125 public Collection<ResourceStats> getResourceStats() 126 { 127 return Collections.unmodifiableCollection(resourceStats.values()); 128 } 129 130 131 132 /** 133 * Retrieve the stats for a given resource. 134 * 135 * @param resourceName The name of the resource. 136 * 137 * @return The stats for the requested resource. 138 */ 139 public ResourceStats getStatsForResource(final String resourceName) 140 { 141 ResourceStats stats = resourceStats.get(resourceName); 142 if(stats == null) 143 { 144 stats = new ResourceStats(resourceName); 145 resourceStats.put(resourceName, stats); 146 } 147 return stats; 148 } 149 150 /** 151 * Retrieve the service provider configuration. 152 * @return The service provider configuration. 153 */ 154 public ServiceProviderConfig getServiceProviderConfig() 155 { 156 final SCIMObject scimObject = new SCIMObject(); 157 final ServiceProviderConfig serviceProviderConfig = 158 ServiceProviderConfig.SERVICE_PROVIDER_CONFIG_RESOURCE_FACTORY. 159 createResource(CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR, 160 scimObject); 161 162 serviceProviderConfig.setId(SCHEMA_URI_CORE); 163 serviceProviderConfig.setPatchConfig(new PatchConfig(true)); 164 serviceProviderConfig.setBulkConfig( 165 new BulkConfig(true, bulkMaxOperations, bulkMaxPayloadSize)); 166 serviceProviderConfig.setFilterConfig(new FilterConfig(true, 167 backend.getConfig().getMaxResults())); 168 serviceProviderConfig.setChangePasswordConfig( 169 new ChangePasswordConfig(true)); 170 serviceProviderConfig.setSortConfig( 171 new SortConfig(backend.supportsSorting())); 172 serviceProviderConfig.setETagConfig(new ETagConfig(false)); 173 174 final List<AuthenticationScheme> authenticationSchemes = 175 new ArrayList<AuthenticationScheme>(); 176 authenticationSchemes.addAll(backend.getSupportedAuthenticationSchemes()); 177 178 if (supportsOAuth) 179 { 180 authenticationSchemes.add(AuthenticationScheme.createOAuth2(false)); 181 } 182 183 serviceProviderConfig.setAuthenticationSchemes(authenticationSchemes); 184 185 serviceProviderConfig.setXmlDataFormatConfig(new XmlDataFormatConfig(true)); 186 187 return serviceProviderConfig; 188 } 189 190 191 192 /** 193 * Retrieves the SCIMBackend used by this SCIMApplication. 194 * 195 * @return The SCIMBackend used by this SCIMApplication. 196 */ 197 public SCIMBackend getBackend() 198 { 199 return backend; 200 } 201 202 203 204 /** 205 * Specify the maximum number of operations permitted in a bulk request. 206 * @param bulkMaxOperations The maximum number of operations permitted in a 207 * bulk request. 208 */ 209 public void setBulkMaxOperations(final long bulkMaxOperations) 210 { 211 this.bulkMaxOperations = bulkMaxOperations; 212 } 213 214 215 216 /** 217 * Specify the maximum payload size in bytes of a bulk request. 218 * @param bulkMaxPayloadSize The maximum payload size in bytes of a bulk 219 * request. 220 */ 221 public void setBulkMaxPayloadSize(final long bulkMaxPayloadSize) 222 { 223 this.bulkMaxPayloadSize = bulkMaxPayloadSize; 224 } 225 226 227 228 /** 229 * Specify the maximum number of concurrent bulk requests. 230 * @param bulkMaxConcurrentRequests The maximum number of concurrent bulk 231 * requests. 232 */ 233 public void setBulkMaxConcurrentRequests(final int bulkMaxConcurrentRequests) 234 { 235 bulkMaxConcurrentRequestsSemaphore.setMaxPermits(bulkMaxConcurrentRequests); 236 } 237 238 239 240 /** 241 * Return the directory that should be used to store temporary files, or 242 * {@code null} for the system dependent default temporary-file 243 * directory (specified by the system property {@code java.io.tmpdir}. 244 * 245 * @return The directory that should be used to store temporary files, or 246 * {@code null} for the system dependent default temporary-file 247 * directory. 248 */ 249 public File getTmpDataDir() 250 { 251 return tmpDataDir; 252 } 253 254 255 256 /** 257 * Return the directory that should be used to store temporary files, or 258 * {@code null} for the system dependent default temporary-file 259 * directory (specified by the system property {@code java.io.tmpdir}. 260 * 261 * @param tmpDataDir The directory that should be used to store temporary 262 * files, or {@code null} for the system dependent default 263 * temporary-file directory. 264 */ 265 public void setTmpDataDir(final File tmpDataDir) 266 { 267 this.tmpDataDir = tmpDataDir; 268 } 269 270 271 272 /** 273 * Attempt to acquire a permit to process a bulk request. 274 * 275 * @throws SCIMException If a permit cannot be immediately obtained. 276 */ 277 public void acquireBulkRequestPermit() 278 throws SCIMException 279 { 280 if (!bulkMaxConcurrentRequestsSemaphore.tryAcquire()) 281 { 282 throw SCIMException.createException( 283 503, "The server is currently processing the maximum number " + 284 "of concurrent bulk requests (" + 285 bulkMaxConcurrentRequestsSemaphore.getMaxPermits() + ")"); 286 } 287 } 288 289 290 291 /** 292 * Release a permit to process a bulk request. 293 */ 294 public void releaseBulkRequestPermit() 295 { 296 bulkMaxConcurrentRequestsSemaphore.release(); 297 } 298}