001/* 002 * Copyright 2011-2016 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; 035import org.glassfish.jersey.server.filter.HttpMethodOverrideFilter; 036 037import java.io.File; 038import java.util.ArrayList; 039import java.util.Collection; 040import java.util.Collections; 041import java.util.HashMap; 042import java.util.List; 043import java.util.Map; 044 045import static com.unboundid.scim.sdk.SCIMConstants.SCHEMA_URI_CORE; 046 047 048/** 049 * This class is a JAX-RS Application that returns the SCIM resource 050 * implementations. 051 */ 052public class SCIMApplication extends WinkApplication 053{ 054 private final Map<String,ResourceStats> resourceStats; 055 private final SCIMBackend backend; 056 private final boolean supportsOAuth; 057 private volatile long bulkMaxOperations = Long.MAX_VALUE; 058 private volatile long bulkMaxPayloadSize = Long.MAX_VALUE; 059 private volatile File tmpDataDir = null; 060 private AdjustableSemaphore bulkMaxConcurrentRequestsSemaphore = 061 new AdjustableSemaphore(Integer.MAX_VALUE); 062 063 064 /** 065 * Create a new SCIMApplication that defines the endpoints provided by the 066 * ResourceDescriptors and uses the provided backend to process the request. 067 * 068 * @param backend The backend that should be used to process the requests. 069 * @param tokenHandler The OAuthTokenHandler implementation to use (this may 070 * be {@code null}. 071 */ 072 public SCIMApplication( 073 final SCIMBackend backend, 074 final OAuthTokenHandler tokenHandler) 075 { 076 register(new RootResource(this)); 077 register(new MonitorResource(this)); 078 079 register(new ServiceProviderConfigResource(this)); 080 register(new XMLServiceProviderConfigResource(this)); 081 register(new JSONServiceProviderConfigResource(this)); 082 083 // The Bulk operation endpoint. 084 register(new BulkResource(this, tokenHandler)); 085 register(new JSONBulkResource(this, tokenHandler)); 086 register(new XMLBulkResource(this, tokenHandler)); 087 088 register(new SCIMResource(this, tokenHandler)); 089 register(new XMLQueryResource(this, tokenHandler)); 090 register(new JSONQueryResource(this, tokenHandler)); 091 092 093 register(new HttpMethodOverrideFilter()); 094 this.resourceStats = new HashMap<String, ResourceStats>(); 095 this.backend = backend; 096 097 if (tokenHandler != null) 098 { 099 supportsOAuth = true; 100 } 101 else 102 { 103 supportsOAuth = false; 104 } 105 } 106 107 /** 108 * Retrieves the statistics for the resources being served by this 109 * application instance. 110 * 111 * @return The statistics for the resources being served by this 112 * application instance. 113 */ 114 public Collection<ResourceStats> getResourceStats() 115 { 116 return Collections.unmodifiableCollection(resourceStats.values()); 117 } 118 119 120 121 /** 122 * Retrieve the stats for a given resource. 123 * 124 * @param resourceName The name of the resource. 125 * 126 * @return The stats for the requested resource. 127 */ 128 public ResourceStats getStatsForResource(final String resourceName) 129 { 130 ResourceStats stats = resourceStats.get(resourceName); 131 if(stats == null) 132 { 133 stats = new ResourceStats(resourceName); 134 resourceStats.put(resourceName, stats); 135 } 136 return stats; 137 } 138 139 /** 140 * Retrieve the service provider configuration. 141 * @return The service provider configuration. 142 */ 143 public ServiceProviderConfig getServiceProviderConfig() 144 { 145 final SCIMObject scimObject = new SCIMObject(); 146 final ServiceProviderConfig serviceProviderConfig = 147 ServiceProviderConfig.SERVICE_PROVIDER_CONFIG_RESOURCE_FACTORY. 148 createResource(CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR, 149 scimObject); 150 151 serviceProviderConfig.setId(SCHEMA_URI_CORE); 152 serviceProviderConfig.setPatchConfig(new PatchConfig(true)); 153 serviceProviderConfig.setBulkConfig( 154 new BulkConfig(true, bulkMaxOperations, bulkMaxPayloadSize)); 155 serviceProviderConfig.setFilterConfig(new FilterConfig(true, 156 backend.getConfig().getMaxResults())); 157 serviceProviderConfig.setChangePasswordConfig( 158 new ChangePasswordConfig(true)); 159 serviceProviderConfig.setSortConfig( 160 new SortConfig(backend.supportsSorting())); 161 serviceProviderConfig.setETagConfig( 162 new ETagConfig(backend.supportsVersioning())); 163 164 final List<AuthenticationScheme> authenticationSchemes = 165 new ArrayList<AuthenticationScheme>(); 166 authenticationSchemes.addAll(backend.getSupportedAuthenticationSchemes()); 167 168 if (supportsOAuth) 169 { 170 authenticationSchemes.add(AuthenticationScheme.createOAuth2(false)); 171 } 172 173 serviceProviderConfig.setAuthenticationSchemes(authenticationSchemes); 174 175 serviceProviderConfig.setXmlDataFormatConfig(new XmlDataFormatConfig(true)); 176 177 return serviceProviderConfig; 178 } 179 180 181 182 /** 183 * Retrieves the SCIMBackend used by this SCIMApplication. 184 * 185 * @return The SCIMBackend used by this SCIMApplication. 186 */ 187 public SCIMBackend getBackend() 188 { 189 return backend; 190 } 191 192 193 194 /** 195 * Specify the maximum number of operations permitted in a bulk request. 196 * @param bulkMaxOperations The maximum number of operations permitted in a 197 * bulk request. 198 */ 199 public void setBulkMaxOperations(final long bulkMaxOperations) 200 { 201 this.bulkMaxOperations = bulkMaxOperations; 202 } 203 204 205 206 /** 207 * Specify the maximum payload size in bytes of a bulk request. 208 * @param bulkMaxPayloadSize The maximum payload size in bytes of a bulk 209 * request. 210 */ 211 public void setBulkMaxPayloadSize(final long bulkMaxPayloadSize) 212 { 213 this.bulkMaxPayloadSize = bulkMaxPayloadSize; 214 } 215 216 217 218 /** 219 * Specify the maximum number of concurrent bulk requests. 220 * @param bulkMaxConcurrentRequests The maximum number of concurrent bulk 221 * requests. 222 */ 223 public void setBulkMaxConcurrentRequests(final int bulkMaxConcurrentRequests) 224 { 225 bulkMaxConcurrentRequestsSemaphore.setMaxPermits(bulkMaxConcurrentRequests); 226 } 227 228 229 230 /** 231 * Return the directory that should be used to store temporary files, or 232 * {@code null} for the system dependent default temporary-file 233 * directory (specified by the system property {@code java.io.tmpdir}. 234 * 235 * @return The directory that should be used to store temporary files, or 236 * {@code null} for the system dependent default temporary-file 237 * directory. 238 */ 239 public File getTmpDataDir() 240 { 241 return tmpDataDir; 242 } 243 244 245 246 /** 247 * Return the directory that should be used to store temporary files, or 248 * {@code null} for the system dependent default temporary-file 249 * directory (specified by the system property {@code java.io.tmpdir}. 250 * 251 * @param tmpDataDir The directory that should be used to store temporary 252 * files, or {@code null} for the system dependent default 253 * temporary-file directory. 254 */ 255 public void setTmpDataDir(final File tmpDataDir) 256 { 257 this.tmpDataDir = tmpDataDir; 258 } 259 260 261 262 /** 263 * Attempt to acquire a permit to process a bulk request. 264 * 265 * @throws SCIMException If a permit cannot be immediately obtained. 266 */ 267 public void acquireBulkRequestPermit() 268 throws SCIMException 269 { 270 if (!bulkMaxConcurrentRequestsSemaphore.tryAcquire()) 271 { 272 throw SCIMException.createException( 273 503, "The server is currently processing the maximum number " + 274 "of concurrent bulk requests (" + 275 bulkMaxConcurrentRequestsSemaphore.getMaxPermits() + ")"); 276 } 277 } 278 279 280 281 /** 282 * Release a permit to process a bulk request. 283 */ 284 public void releaseBulkRequestPermit() 285 { 286 bulkMaxConcurrentRequestsSemaphore.release(); 287 } 288}