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    }