001 /*
002 * Copyright 2011-2012 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
018 package com.unboundid.scim.wink;
019
020 import com.unboundid.scim.data.AuthenticationScheme;
021 import com.unboundid.scim.data.BulkConfig;
022 import com.unboundid.scim.data.ChangePasswordConfig;
023 import com.unboundid.scim.data.ETagConfig;
024 import com.unboundid.scim.data.FilterConfig;
025 import com.unboundid.scim.data.PatchConfig;
026 import com.unboundid.scim.data.ServiceProviderConfig;
027 import com.unboundid.scim.data.SortConfig;
028 import com.unboundid.scim.data.XmlDataFormatConfig;
029 import com.unboundid.scim.sdk.ResourceSchemaBackend;
030 import com.unboundid.scim.schema.CoreSchema;
031 import com.unboundid.scim.schema.ResourceDescriptor;
032 import com.unboundid.scim.sdk.SCIMBackend;
033 import com.unboundid.scim.sdk.SCIMException;
034 import com.unboundid.scim.sdk.SCIMObject;
035 import org.apache.wink.common.WinkApplication;
036
037 import java.io.File;
038 import java.util.ArrayList;
039 import java.util.Collection;
040 import java.util.Collections;
041 import java.util.HashMap;
042 import java.util.HashSet;
043 import java.util.List;
044 import java.util.Map;
045 import java.util.Set;
046
047 import static com.unboundid.scim.sdk.SCIMConstants.SCHEMA_URI_CORE;
048
049
050 /**
051 * This class is a JAX-RS Application that returns the SCIM resource
052 * implementations.
053 */
054 public class SCIMApplication extends WinkApplication
055 {
056 private final Set<Object> instances;
057 private final Map<String,ResourceDescriptor> descriptors;
058 private final Map<String,ResourceStats> resourceStats;
059 private final SCIMBackend backend;
060 private volatile long bulkMaxOperations = Long.MAX_VALUE;
061 private volatile long bulkMaxPayloadSize = Long.MAX_VALUE;
062 private volatile File tmpDataDir = null;
063 private AdjustableSemaphore bulkMaxConcurrentRequestsSemaphore =
064 new AdjustableSemaphore(Integer.MAX_VALUE);
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 resourceDescriptors The ResourceDescriptors to serve.
071 * @param backend The backend that should be used to process the requests.
072 */
073 public SCIMApplication(
074 final Collection<ResourceDescriptor> resourceDescriptors,
075 final SCIMBackend backend)
076 {
077 descriptors =
078 new HashMap<String, ResourceDescriptor>(resourceDescriptors.size());
079 for (final ResourceDescriptor descriptor : resourceDescriptors)
080 {
081 descriptors.put(descriptor.getEndpoint(), descriptor);
082 }
083
084 instances = new HashSet<Object>(resourceDescriptors.size() * 4 + 12);
085 Collection<ResourceStats> statsCollection =
086 new ArrayList<ResourceStats>(resourceDescriptors.size() + 2);
087
088 ResourceStats stats = new ResourceStats("monitor");
089 instances.add(new MonitorResource(this, stats));
090 statsCollection.add(stats);
091
092 stats = new ResourceStats(
093 CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR.getName());
094 instances.add(new ServiceProviderConfigResource(this, stats));
095 instances.add(new XMLServiceProviderConfigResource(this, stats));
096 instances.add(new JSONServiceProviderConfigResource(this, stats));
097 statsCollection.add(stats);
098
099 stats = new ResourceStats(
100 CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR.getName());
101 // The resources for the /Schema and /Schemas endpoints.
102 ResourceSchemaBackend resourceSchemaBackend =
103 new ResourceSchemaBackend(resourceDescriptors);
104 instances.add(new SCIMResource(CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR,
105 stats, resourceSchemaBackend));
106 instances.add(new XMLQueryResource(CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR,
107 stats, resourceSchemaBackend));
108 instances.add(new JSONQueryResource(CoreSchema.RESOURCE_SCHEMA_DESCRIPTOR,
109 stats, resourceSchemaBackend));
110 statsCollection.add(stats);
111
112 for(ResourceDescriptor resourceDescriptor : resourceDescriptors)
113 {
114 stats = new ResourceStats(resourceDescriptor.getName());
115 instances.add(new SCIMResource(resourceDescriptor, stats, backend));
116 instances.add(new XMLQueryResource(resourceDescriptor, stats, backend));
117 instances.add(new JSONQueryResource(resourceDescriptor, stats, backend));
118 statsCollection.add(stats);
119 }
120
121 // The Bulk operation endpoint.
122 stats = new ResourceStats("Bulk");
123 instances.add(new BulkResource(this, stats, backend));
124 instances.add(new JSONBulkResource(this, stats, backend));
125 instances.add(new XMLBulkResource(this, stats, backend));
126 statsCollection.add(stats);
127
128 this.resourceStats =
129 new HashMap<String, ResourceStats>(statsCollection.size());
130 for (final ResourceStats s : statsCollection)
131 {
132 resourceStats.put(s.getName(), s);
133 }
134 this.backend = backend;
135 }
136
137
138
139 @Override
140 public Set<Object> getInstances()
141 {
142 return instances;
143 }
144
145
146 /**
147 * Retrieves the statistics for the resources being served by this
148 * application instance.
149 *
150 * @return The statistics for the resources being served by this
151 * application instance.
152 */
153 public Collection<ResourceStats> getResourceStats()
154 {
155 return Collections.unmodifiableCollection(resourceStats.values());
156 }
157
158
159
160 /**
161 * Retrieve the stats for a given resource.
162 *
163 * @param resourceName The name of the resource.
164 *
165 * @return The stats for the requested resource.
166 */
167 public ResourceStats getStatsForResource(final String resourceName)
168 {
169 return resourceStats.get(resourceName);
170 }
171
172 /**
173 * Retrieve the service provider configuration.
174 * @return The service provider configuration.
175 */
176 public ServiceProviderConfig getServiceProviderConfig()
177 {
178 final SCIMObject scimObject = new SCIMObject();
179 final ServiceProviderConfig serviceProviderConfig =
180 ServiceProviderConfig.SERVICE_PROVIDER_CONFIG_RESOURCE_FACTORY.
181 createResource(CoreSchema.SERVICE_PROVIDER_CONFIG_SCHEMA_DESCRIPTOR,
182 scimObject);
183
184 serviceProviderConfig.setId(SCHEMA_URI_CORE);
185 serviceProviderConfig.setPatchConfig(new PatchConfig(false));
186 serviceProviderConfig.setBulkConfig(
187 new BulkConfig(true, bulkMaxOperations, bulkMaxPayloadSize));
188 serviceProviderConfig.setFilterConfig(new FilterConfig(true,
189 backend.getConfig().getMaxResults()));
190 serviceProviderConfig.setChangePasswordConfig(
191 new ChangePasswordConfig(false));
192 serviceProviderConfig.setSortConfig(new SortConfig(false));
193 serviceProviderConfig.setETagConfig(new ETagConfig(false));
194
195 final List<AuthenticationScheme> authenticationSchemes =
196 new ArrayList<AuthenticationScheme>();
197 authenticationSchemes.add(
198 new AuthenticationScheme(
199 "HttpBasic",
200 "The HTTP Basic Access Authentication scheme. This scheme is not " +
201 "considered to be a secure method of user authentication (unless " +
202 "used in conjunction with some external secure system such as " +
203 "SSL), as the user name and password are passed over the network " +
204 "as cleartext.",
205 "http://www.ietf.org/rfc/rfc2617.txt",
206 "http://en.wikipedia.org/wiki/Basic_access_authentication",
207 null, false));
208 serviceProviderConfig.setAuthenticationSchemes(authenticationSchemes);
209
210 serviceProviderConfig.setXmlDataFormatConfig(new XmlDataFormatConfig(true));
211
212 return serviceProviderConfig;
213 }
214
215
216
217 /**
218 * Specify the maximum number of operations permitted in a bulk request.
219 * @param bulkMaxOperations The maximum number of operations permitted in a
220 * bulk request.
221 */
222 public void setBulkMaxOperations(final long bulkMaxOperations)
223 {
224 this.bulkMaxOperations = bulkMaxOperations;
225 }
226
227
228
229 /**
230 * Specify the maximum payload size in bytes of a bulk request.
231 * @param bulkMaxPayloadSize The maximum payload size in bytes of a bulk
232 * request.
233 */
234 public void setBulkMaxPayloadSize(final long bulkMaxPayloadSize)
235 {
236 this.bulkMaxPayloadSize = bulkMaxPayloadSize;
237 }
238
239
240
241 /**
242 * Specify the maximum number of concurrent bulk requests.
243 * @param bulkMaxConcurrentRequests The maximum number of concurrent bulk
244 * requests.
245 */
246 public void setBulkMaxConcurrentRequests(final int bulkMaxConcurrentRequests)
247 {
248 bulkMaxConcurrentRequestsSemaphore.setMaxPermits(bulkMaxConcurrentRequests);
249 }
250
251
252
253 /**
254 * Retrieve the resource descriptors keyed by name of endpoint.
255 * @return The resource descriptors keyed by name of endpoint.
256 */
257 public Map<String, ResourceDescriptor> getDescriptors()
258 {
259 return descriptors;
260 }
261
262
263
264 /**
265 * Return the directory that should be used to store temporary files, or
266 * {@code null} for the system dependent default temporary-file
267 * directory (specified by the system property {@code java.io.tmpdir}.
268 *
269 * @return The directory that should be used to store temporary files, or
270 * {@code null} for the system dependent default temporary-file
271 * directory.
272 */
273 public File getTmpDataDir()
274 {
275 return tmpDataDir;
276 }
277
278
279
280 /**
281 * Return the directory that should be used to store temporary files, or
282 * {@code null} for the system dependent default temporary-file
283 * directory (specified by the system property {@code java.io.tmpdir}.
284 *
285 * @param tmpDataDir The directory that should be used to store temporary
286 * files, or {@code null} for the system dependent default
287 * temporary-file directory.
288 */
289 public void setTmpDataDir(final File tmpDataDir)
290 {
291 this.tmpDataDir = tmpDataDir;
292 }
293
294
295
296 /**
297 * Attempt to acquire a permit to process a bulk request.
298 *
299 * @throws SCIMException If a permit cannot be immediately obtained.
300 */
301 public void acquireBulkRequestPermit()
302 throws SCIMException
303 {
304 if (!bulkMaxConcurrentRequestsSemaphore.tryAcquire())
305 {
306 throw SCIMException.createException(
307 503, "The server is currently processing the maximum number " +
308 "of concurrent bulk requests (" +
309 bulkMaxConcurrentRequestsSemaphore.getMaxPermits() + ")");
310 }
311 }
312
313
314
315 /**
316 * Release a permit to process a bulk request.
317 */
318 public void releaseBulkRequestPermit()
319 {
320 bulkMaxConcurrentRequestsSemaphore.release();
321 }
322 }