UnboundID Server SDK

UnboundID Server SDK Documentation

ExampleJDBCStoreAdapter.java

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * docs/licenses/cddl.txt
 * or http://www.opensource.org/licenses/cddl1.php.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * docs/licenses/cddl.txt.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 * Copyright 2013 UnboundID Corp.
 */

package com.unboundid.directory.sdk.examples;

import com.unboundid.directory.sdk.broker.api.StoreAdapter;
import com.unboundid.directory.sdk.broker.config.StoreAdapterConfig;
import com.unboundid.directory.sdk.broker.types.IdentityBrokerContext;
import com.unboundid.directory.sdk.broker.types.StoreCreateRequest;
import com.unboundid.directory.sdk.broker.types.StoreDeleteRequest;
import com.unboundid.directory.sdk.broker.types.StoreSearchRequest;
import com.unboundid.directory.sdk.broker.types.StoreUpdateRequest;
import com.unboundid.directory.sdk.common.types.LogSeverity;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.scim.data.AttributeValueResolver;
import com.unboundid.scim.data.BaseResource;
import com.unboundid.scim.data.Meta;
import com.unboundid.scim.marshal.json.JsonMarshaller;
import com.unboundid.scim.marshal.json.JsonUnmarshaller;
import com.unboundid.scim.schema.ResourceDescriptor;
import com.unboundid.scim.sdk.AttributePath;
import com.unboundid.scim.sdk.Diff;
import com.unboundid.scim.sdk.InvalidResourceException;
import com.unboundid.scim.sdk.PageParameters;
import com.unboundid.scim.sdk.ResourceNotFoundException;
import com.unboundid.scim.sdk.Resources;
import com.unboundid.scim.sdk.SCIMException;
import com.unboundid.scim.sdk.SCIMFilter;
import com.unboundid.scim.sdk.SCIMFilterType;
import com.unboundid.scim.sdk.SCIMObject;
import com.unboundid.scim.sdk.SCIMQueryAttributes;
import com.unboundid.scim.sdk.ServerErrorException;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.Validator;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.StringArgument;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import static com.unboundid.scim.sdk.SCIMConstants.SCHEMA_URI_CORE;
import static com.unboundid.scim.data.BaseResource.BASE_RESOURCE_FACTORY;
import static com.unboundid.scim.schema.CoreSchema.USER_DESCRIPTOR;

/**
 * Example implementation of a Dataview StoreAdapter persisting to a Java jdbc
 * RDBMS backing store. This adapter depends on a jdbc driver jar which must be
 * copied into server's lib directory. The defaults in this example work with
 * <a href="http://db.apache.org/derby/releases/release-10.10.1.1.cgi">Apache
 * derby 10.10.1.1</a>
 */
public class ExampleJDBCStoreAdapter extends StoreAdapter
{
  /**
   * The name of the argument that will be used to load the jdbc driver class.
   */
  public static final String ARG_NAME_JDBC_DRIVER = "jdbc-driver-class";

  /**
   * The name of the argument that will be used to specify the jdbc url to the
   * database instance used by this adapter.
   */
  public static final String ARG_NAME_JDBC_URL = "jdbc-url";

  /**
   * The name of the argument that will be used to specify the file name to
   * initialize the database schema used by this adapter.
   */
  public static final String ARG_NAME_INIT_SQL_SCHEMA_FILE =
    "init-sql-schema-path";



  // JDBC driver for the store adapter.
  private String jdbcDriverClass;

  // JDBC url for the store adapter.
  private String jdbcUrl;

  // Connection to JDBC database.
  private Connection connection;

  // Handle to the ServerContext object.
  private IdentityBrokerContext serverContext;

  private static final AttributeValueResolver<String> SINGULAR =
    AttributeValueResolver.STRING_RESOLVER;


  /**
   * {@inheritDoc}
   */
  @Override
  public String getExtensionName()
  {
    return "Simple JDBC Store Adapter";
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public String[] getExtensionDescription()
  {
    return new String[] {
      "Implements a simple Store Adapter for a Broker DataView which is " +
      "persisted in an embeddable JDBC backing store.  This example persists " +
      "user schema to a single table structure in user-defined tablespace."
    };
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public void defineConfigArguments(final ArgumentParser parser)
    throws ArgumentException
  {
    StringArgument jdbcDriverClassStringArg =
      new StringArgument(null, ARG_NAME_JDBC_DRIVER, true, 1,
        "{jdbc-driver-class}",
        "The JDBC driver class name used by this store adapter.",
        "org.apache.derby.jdbc.EmbeddedDriver");
    parser.addArgument(jdbcDriverClassStringArg);

    StringArgument jdbcUrlStringArg =
      new StringArgument(null, ARG_NAME_JDBC_URL, true, 1, "{jdbc-url}",
        "The JDBC url to the database instance used by this store adapter.",
        "jdbc:derby:storeadapter;create=true");
    parser.addArgument(jdbcUrlStringArg);

    final FileArgument initSqlSchemaFileArg = new FileArgument(null,
      ARG_NAME_INIT_SQL_SCHEMA_FILE, true, 1, "{init-sql-schema-path}",
      "The path to the file containing the create table command used to " +
      "initialize the database.", false, true, true, false, Arrays.asList(
        new File("resource/example-jdbc-store-adapter/create-scim-table.sql")
    ));
    parser.addArgument(initSqlSchemaFileArg);
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public void initializeStoreAdapter(final IdentityBrokerContext serverContext,
                                     final StoreAdapterConfig config,
                                     final ArgumentParser parser)
    throws IOException, LDAPException, SQLException
  {
    this.serverContext = serverContext;

    StringArgument jdbcDriverArgument =
      (StringArgument) parser.getNamedArgument(ARG_NAME_JDBC_DRIVER);
    jdbcDriverClass = jdbcDriverArgument.getValue();
    Validator.ensureNotNull(jdbcDriverClass);

    try
    {
      // Some JDBC drivers require their static and default constructors to be
      // called for the driver to be properly registered with the DriverManager.
      Class.forName(jdbcDriverClass).newInstance();
    }
    catch (Exception e)
    {
      throw new RuntimeException(e);
    }

    StringArgument jdbcUrlArgument =
      (StringArgument) parser.getNamedArgument(ARG_NAME_JDBC_URL);
    jdbcUrl = jdbcUrlArgument.getValue();
    Validator.ensureNotNull(jdbcUrl);

    connection = DriverManager.getConnection(jdbcUrl);

    // check to see if the schema exists
    FileArgument schemaFileArg =
      (FileArgument) parser.getNamedArgument(ARG_NAME_INIT_SQL_SCHEMA_FILE);
    File sqlSchemaFile = schemaFileArg.getValue();
    if(!sqlSchemaFile.isAbsolute())
    {
      sqlSchemaFile = new File(serverContext.getServerRoot(),
        sqlSchemaFile.getPath());
    }
    File schemaCreatedFlag = new File(sqlSchemaFile.getParentFile(),
      ".example-jdbc-schema-created");
    if (!schemaCreatedFlag.exists())
    {
      BufferedReader reader = new BufferedReader(new FileReader(sqlSchemaFile));
      StringBuffer buffer = new StringBuffer();
      String line;
      while ((line = reader.readLine()) != null)
      {
        if (line.trim().startsWith("--"))
        {
          continue;
        }
        buffer.append(line);
      }
      Statement createSql = connection.createStatement();
      createSql.execute(buffer.toString());
      schemaCreatedFlag.createNewFile();
    }
  }


  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAvailable()
  {
    if (connection != null && serverContext != null)
    {
      try
      {
        return connection.isValid(10);
      }
      catch (SQLException e)
      {
        serverContext.logMessage(LogSeverity.SEVERE_ERROR,
                "Cannot connect to database: " + e.getMessage());
        return false;
      }
    }
    else
    {
      return false;
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public ResourceDescriptor getNativeSchema()
  {
    return USER_DESCRIPTOR;
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public Resources<? extends BaseResource> search(
    final StoreSearchRequest request) throws SCIMException
  {
    final SCIMFilter filter = request.getSCIMFilter();

    final AttributePath idAttrPath =
      new AttributePath(SCHEMA_URI_CORE, "id", null);

    try
    {
      if (filter != null &&
        filter.getFilterType().equals(SCIMFilterType.EQUALITY) &&
        filter.getFilterAttribute().toString().equals(idAttrPath.toString()))
      {
        //This is a search by SCIM ID
        ResultSet resultSet = queryScimResourceById(filter.getFilterValue(),
          true);
        if (resultSet.next())
        {
          BaseResource resource = loadScimResourceFromResultSet(resultSet);
          removeSensitiveAttributes(resource.getScimObject());
          return new Resources<BaseResource>(
            Collections.singletonList(resource));
        }
        else
        {
          return new Resources<BaseResource>(
            Collections.<BaseResource>emptyList());
        }
      }
      else
      {
        final PageParameters pageParams = request.getPageParameters();
        final List<BaseResource> resultList = new LinkedList<BaseResource>();

        int index = 0;
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(ScimSqlStatement.SELECT_ALL
          .getStatement());
        while (resultSet.next())
        {
          BaseResource resource = loadScimResourceFromResultSet(resultSet);
          if (filter == null || resource.getScimObject().matchesFilter(filter))
          {
            index++;
            if (pageParams != null && pageParams.getStartIndex() > index)
            {
              continue;
            }

            if (pageParams != null && index > pageParams.getCount())
            {
              break;
            }

            resultList.add(createParedResource(resource.getScimObject(),
              request.getSCIMQueryAttributes()));
          }
        }
        return new Resources<BaseResource>(resultList);
      }
    }
    catch (final SQLException se)
    {
      serverContext.debugCaught(se);
      throw new ServerErrorException(StaticUtils.getExceptionMessage(se));
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public BaseResource create(final StoreCreateRequest request)
    throws SCIMException
  {
    try
    {
      //Populate the SCIM 'id' and 'meta' attributes
      final BaseResource resourceToCreate = request.getResource();
      resourceToCreate.setId(UUID.randomUUID().toString());
      final URI location = URI.create("/" + resourceToCreate.getId());
      final Meta meta = new Meta(new Date(), null, location, null);
      resourceToCreate.setMeta(meta);

      List<String> parameters = getParametersFromResource(resourceToCreate);
      PreparedStatement preparedStatement = ScimSqlStatement.INSERT
        .getPreparedStatement(connection, parameters
          .toArray(new String[parameters.size()]));
      preparedStatement.executeUpdate();


      return createParedResource(resourceToCreate.getScimObject(),
        request.getSCIMQueryAttributes());
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      throw new ServerErrorException(StaticUtils.getExceptionMessage(e));
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public BaseResource update(final StoreUpdateRequest request)
    throws SCIMException
  {
    checkIfIdExists(request.getId());

    try
    {
      BaseResource resource = loadScimResourceById(request.getId());

      final Diff<BaseResource> diff = Diff.fromPartialResource(
                    request.getPartialResource(), false);

      //Apply the PATCH modifications to the resource
      BaseResource resourceToUpdate = diff.apply(resource,
        BASE_RESOURCE_FACTORY);

      //Update the 'meta.lastModified' attribute
      final Meta meta = resource.getMeta();
      meta.setLastModified(new Date());
      resource.setMeta(meta);

      Validator.ensureNotNull(resourceToUpdate);
      final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      try
      {
        new JsonMarshaller().marshal(resourceToUpdate, outputStream);
        List<String> parameters = getParametersFromResource(resource);
        parameters.remove(0);
        parameters.add(resourceToUpdate.getId());
        PreparedStatement preparedStatement = ScimSqlStatement.UPDATE
          .getPreparedStatement(connection, parameters.toArray(
            new String[parameters.size()]));
        preparedStatement.executeUpdate();
      }
      finally
      {
        safeCloseStream(outputStream);
      }

      return createParedResource(resourceToUpdate.getScimObject(),
        request.getSCIMQueryAttributes());
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      throw new ServerErrorException(StaticUtils.getExceptionMessage(e));
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override
  public void delete(final StoreDeleteRequest request) throws SCIMException
  {
    checkIfIdExists(request.getId());

    try
    {
      PreparedStatement preparedStatement = ScimSqlStatement.DELETE
        .getPreparedStatement(connection, request.getId());
      preparedStatement.execute();
    }
    catch (final SQLException se)
    {
      serverContext.debugCaught(se);
      throw new ServerErrorException(StaticUtils.getExceptionMessage(se));
    }
  }


  /**
   * Checks to see if a resource exists.
   *
   * @param id to check
   * @throws ResourceNotFoundException if a resource was not found
   */
  private void checkIfIdExists(final String id) throws ResourceNotFoundException
  {
    ResultSet resultSet = queryScimResourceById(id, false);
    try
    {
      if (!resultSet.next())
      {
        throw new ResourceNotFoundException(String.format(
          "No resource found with ID '%s'", id));
      }
    }
    catch (final SQLException se)
    {
      serverContext.debugCaught(se);
      throw new ResourceNotFoundException(StaticUtils.getExceptionMessage(se));
    }
  }


  /**
   * Loads a scim resource by id.
   *
   * @param id to load from the database
   * @return BaseResource from the database
   * @throws ResourceNotFoundException if the resource cannot be found
   * @throws InvalidResourceException if the resource is invalid
   */
  private BaseResource loadScimResourceById(final String id)
    throws ResourceNotFoundException, InvalidResourceException
  {
    ResultSet resultSet = queryScimResourceById(id, true);
    try
    {
      if (!resultSet.next())
      {
        throw new ResourceNotFoundException(String.format(
          "No resource found with ID '%s'", id));
      }
      return loadScimResourceFromResultSet(resultSet);
    }
    catch (final SQLException se)
    {
      serverContext.debugCaught(se);
      throw new ResourceNotFoundException(StaticUtils.getExceptionMessage(se));
    }
  }


  /**
   * Loads a scim resource from the current database ResultSet.
   *
   * @param resultSet to load a resource from
   * @return BaseResource loaded from the ResultSet
   * @throws InvalidResourceException if the resource is invalid
   * @throws SQLException if a problem occurred
   */
  private BaseResource loadScimResourceFromResultSet(final ResultSet resultSet)
    throws InvalidResourceException, SQLException
  {
    final ResourceDescriptor schema = getNativeSchema();
    final JsonUnmarshaller jsonUnmarshaller = new JsonUnmarshaller();
    return jsonUnmarshaller.unmarshal(resultSet.getAsciiStream("json"), schema,
      BASE_RESOURCE_FACTORY);
  }


  /**
   * Queries the database for a scim resource by id.
   *
   * @param id to query in the database
   * @param loadRecord {@code true} to query full record,
   *                   {@code false} otherwise
   * @return ResultSet referencing a found scim resource
   * @throws ResourceNotFoundException if the resource cannot be found
   */
  private ResultSet queryScimResourceById(final String id,
                                          final boolean loadRecord)
    throws ResourceNotFoundException
  {
    try
    {
      Statement statement = connection.createStatement();
      PreparedStatement preparedStatement;
      if (loadRecord)
      {
        preparedStatement = ScimSqlStatement.SELECT_CONTENT
          .getPreparedStatement(connection, id);
      }
      else
      {
        preparedStatement = ScimSqlStatement.SELECT_ID
          .getPreparedStatement(connection, id);
      }
      return preparedStatement.executeQuery();
    }
    catch (final SQLException se)
    {
      serverContext.debugCaught(se);
      throw new ResourceNotFoundException(StaticUtils.getExceptionMessage(se));
    }
  }


  /**
   * Creates a new resource from a scim object with specific attributes.
   *
   * @param scimObject to create a resource from
   * @param attributes to include in the resource
   * @return BaseResource created from the scim object
   */
  private BaseResource createParedResource(final SCIMObject scimObject,
                                           final SCIMQueryAttributes attributes)
  {
    //Pare the returned resource down to the attributes that were requested
    final SCIMObject paredObject = attributes.pareObject(scimObject);
    removeSensitiveAttributes(paredObject);
    final BaseResource newResource = new BaseResource(
      getNativeSchema(), paredObject);

    return newResource;
  }


  /**
   * Removes any sensitive attributes from a scim object.
   *
   * @param scimObject to process
   */
  private void removeSensitiveAttributes(final SCIMObject scimObject)
  {
    scimObject.removeAttribute(SCHEMA_URI_CORE, "password");
  }


  /**
   * Returns a resource as a series of sql parameters.
   *
   * @param resource to parameterize
   * @return resource as parameters
   * @throws SCIMException if problem occurs
   */
  private List<String> getParametersFromResource(final BaseResource resource)
    throws SCIMException
  {
    final List<String> parameters = new ArrayList<String>();
    final JsonMarshaller marshaller = new JsonMarshaller();
    ByteArrayOutputStream outputStream = null;
    try
    {
      parameters.add(resource.getId());
      parameters.add(resource.getExternalId());
      parameters.add(resource.getMeta().toString());
      parameters.add(getSingularAttribute(resource, "userName"));
      parameters.add(getSingularAttribute(resource, "name.formatted"));
      parameters.add(getSingularAttribute(resource, "name.familyName"));
      parameters.add(getSingularAttribute(resource, "name.givenName"));
      parameters.add(getSingularAttribute(resource, "name.middleName"));
      parameters.add(getSingularAttribute(resource, "name.honorificPrefix"));
      parameters.add(getSingularAttribute(resource, "name.honorificSuffix"));
      parameters.add(getSingularAttribute(resource, "displayName"));
      parameters.add(getSingularAttribute(resource, "nickName"));
      parameters.add(getSingularAttribute(resource, "profileUrl"));
      parameters.add(getSingularAttribute(resource, "title"));
      parameters.add(getSingularAttribute(resource, "preferredLanguage"));
      parameters.add(getSingularAttribute(resource, "locale"));
      parameters.add(getSingularAttribute(resource, "timezone"));

      outputStream = new ByteArrayOutputStream();
      marshaller.marshal(resource, outputStream);
      JSONObject jsonObject = null;
      try
      {
        jsonObject = new JSONObject(outputStream.toString());
        parameters.add(getComplexAttribute(resource, jsonObject, "emails"));
        parameters.add(getComplexAttribute(resource, jsonObject, "addresses"));
        parameters.add(getComplexAttribute(resource, jsonObject,
          "phonenumbers"));
        parameters.add(getComplexAttribute(resource, jsonObject, "photos"));
        parameters.add(getComplexAttribute(resource, jsonObject, "groups"));
        parameters.add(getComplexAttribute(resource, jsonObject,
          "entitlements"));
        parameters.add(getComplexAttribute(resource, jsonObject, "roles"));
      }
      catch (final JSONException je)
      {
        throw new RuntimeException(je);
      }
      parameters.add(getSingularAttribute(resource, "website"));
      parameters.add(getSingularAttribute(resource, "gender"));
      parameters.add(outputStream.toString());
    }
    finally
    {
      safeCloseStream(outputStream);
    }
    return parameters;
  }


  /**
   * Retrieves a single attribute from a resource.
   *
   * @param resource to access
   * @param attribute to retrieve
   * @return attribute value
   */
  private String getSingularAttribute(final BaseResource resource,
                                      final String attribute)
  {
    String value = null;
    if (attribute.contains("."))
    {
      AttributePath attributePath = AttributePath.parse(attribute);
      String parentAttributeName = attributePath.getAttributeName();
      SCIMObject object = resource.getScimObject();
      if (object.hasAttribute(SCHEMA_URI_CORE, parentAttributeName))
      {
        String subAttribute = attributePath.getSubAttributeName();
        value = object.getAttribute(SCHEMA_URI_CORE, parentAttributeName)
          .getValue().getSubAttributeValue(subAttribute, SINGULAR);
      }
    }
    else
    {
      value = resource.getSingularAttributeValue(SCHEMA_URI_CORE,
        attribute, SINGULAR);
    }
    return value == null ? "" : value;
  }


  /**
   * Retrieves a complex or multi-value attribute from a resource.
   *
   * @param resource to access
   * @param object resource in json format
   * @param attribute to retrieve
   * @return attribute content in json
   * @throws JSONException if a problem occurs
   */
  private String getComplexAttribute(final BaseResource resource,
                                     final JSONObject object,
                                     final String attribute)
    throws JSONException
  {
    String value = "";
    if (resource.getScimObject().hasAttribute(SCHEMA_URI_CORE, attribute))
    {
      value = object.getJSONObject(attribute).toString();
    }
    return value;
  }


  /**
   * Closes a stream quietly.
   *
   * @param stream to close
   */
  private void safeCloseStream(final Closeable stream)
  {
    if (stream != null)
    {
      try
      {
        stream.close();
      }
      catch (final IOException ioe)
      {  }
    }
  }


  /**
   * Enumerated templates for sql commands. These commands could be moved to
   * resources for customization.
   */
  enum ScimSqlStatement
  {
    /**
     * Insert into scim resources.
     */
    INSERT("insert into %s (id, externalid, meta, username, name, " +
           "familyname, givenname, middlename, honorificprefix, " +
           "honorificsuffix, displayname, nickname, profileurl, title, " +
           "preferredlanguage, locale, timezone, emails, addresses, " +
           "phonenumbers, photos, groups, entitlements, roles, website, " +
           "gender, json) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " +
           "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
    /**
     * Update scim resource by id.
     */
    UPDATE("update %s set " +
      "externalid = ?, meta = ?, username = ?, name = ?, familyname = ?, " +
      "givenname = ?, middlename = ?, honorificprefix = ?, " +
      "honorificsuffix = ?, displayname = ?, nickname = ?, profileurl = ?, " +
      "title = ?, preferredlanguage = ?, locale = ?, timezone = ?, " +
      "emails = ?, addresses = ?, phonenumbers = ?, photos = ?, groups = ?, " +
      "entitlements = ?, roles = ?, website = ?, gender = ?, json = ? " +
      "where id = ?"),
    /**
     * Delete scim resource by id.
     */
    DELETE("delete from %s where id = ?"),
    /**
     * Select all scim resources.
     */
    SELECT_ALL("select * from %s"),
    /**
     * Load scim resource by id.
     */
    SELECT_CONTENT("select id, json from %s where id = ?"),
    /**
     * Check for scim resource only by id.
     */
    SELECT_ID("select id from %s where id = ?");


    /**
     * Single table containing scim resources. This table is created by the
     * sql statement in config/create-scim-table.sql.
     */
    public static final String TABLE = "SCIM_RESOURCES";

    private final String statement;

    /**
     * enum constructor.
     *
     * @param statement embedded statement
     */
    ScimSqlStatement(final String statement)
    {
      this.statement = statement;
    }

    /**
     * Returns a rendered sql statement.
     *
     * @return String containing the rendered sql statement
     */
    public String getStatement()
    {
      return String.format(statement, TABLE);
    }

    /**
     * Returns a rendered prepared statement from the supplied parameters.
     *
     * @param connection sql connection
     * @param parameters parameters used for substitutions
     * @return PreparedStatement rendered result
     * @throws SQLException if a problem occurs
     */
    public PreparedStatement getPreparedStatement(final Connection connection,
                                                  final String... parameters)
      throws SQLException
    {
      final PreparedStatement preparedStatement =
        connection.prepareStatement(getStatement());
      if (parameters != null)
      {
        int parameterIndex = 1;
        for (final String parameter : parameters)
        {
          preparedStatement.setString(parameterIndex++, parameter);
        }
      }

      return preparedStatement;
    }
  }
}