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}