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}