37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * Copyright © 2011-2015 ForgeRock AS. All rights reserved.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * The contents of this file are subject to the terms
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * of the Common Development and Distribution License
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * (the License). You may not use this file except in
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * compliance with the License.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * You can obtain a copy of the License at
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * See the License for the specific language governing
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * permission and limitations under the License.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * When distributing Covered Code, include this CDDL
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Header Notice in each file and include the License file
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * If applicable, add the following below the CDDL Header,
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * with the fields enclosed by brackets [] replaced by
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * your own identifying information:
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * "Portions Copyrighted [year] [name of copyright owner]"
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchenerimport static org.forgerock.json.resource.Responses.newResourceResponse;
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienleimport static org.forgerock.openidm.repo.QueryConstants.PAGED_RESULTS_OFFSET;
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienleimport static org.forgerock.openidm.repo.QueryConstants.PAGE_SIZE;
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienleimport static org.forgerock.openidm.repo.QueryConstants.SORT_KEYS;
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmillerimport static org.forgerock.openidm.repo.util.Clauses.where;
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchenerimport com.fasterxml.jackson.core.type.TypeReference;
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchenerimport com.fasterxml.jackson.databind.ObjectMapper;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.json.resource.InternalServerErrorException;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.json.resource.NotFoundException;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.json.resource.PreconditionFailedException;
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchenerimport org.forgerock.json.resource.ResourceResponse;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.json.resource.ResourceException;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.openidm.repo.jdbc.ErrorType;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.openidm.repo.jdbc.SQLExceptionHandler;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleimport org.forgerock.openidm.repo.jdbc.TableHandler;
37c999796383567df3db1f9d2a09942d83d413caChad Kienleimport org.forgerock.openidm.repo.jdbc.impl.query.QueryResultMapper;
37c999796383567df3db1f9d2a09942d83d413caChad Kienleimport org.forgerock.openidm.repo.jdbc.impl.query.TableQueries;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Handling of tables in a generic (not object specific) layout
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienlepublic class GenericTableHandler implements TableHandler {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle final static Logger logger = LoggerFactory.getLogger(GenericTableHandler.class);
5848ca5e742e748dfd18fc53d810fa017949f809Jim Mitchener * Maximum length of searchable properties.
5848ca5e742e748dfd18fc53d810fa017949f809Jim Mitchener * This is used to trim values due to database index size limitations.
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller protected static final int SEARCHABLE_LENGTH = 2000;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Jackson parser
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Type information for the Jackson parser
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle final TypeReference<LinkedHashMap<String,Object>> typeRef = new TypeReference<LinkedHashMap<String,Object>>() {};
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle final boolean enableBatching; // Whether to use JDBC statement batching.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int maxBatchSize; // The maximum number of statements to batch together. If max batch size is 1, do not use batching.
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener public boolean queryIdExists(String queryId) {
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * Create a generic table handler using a QueryFilterVisitor that uses generic object property tables to process
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * query filters.
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * @param tableConfig the table config
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * @param dbSchemaName the schem name
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * @param queriesConfig a map of named queries
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * @param commandsConfig a map of named commands
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * @param maxBatchSize the maximum batch size
a17f398e6526e91a7a3df8dbe0e7f8dd1e8e581dBrendan Mmiller * @param sqlExceptionHandler a handler for SQLExceptions
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller public GenericTableHandler(JsonValue tableConfig,
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle this.sqlExceptionHandler = new DefaultSQLExceptionHandler();
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller queries = new TableQueries(this, mainTableName, propTableName, dbSchemaName, getSearchableLength(), new GenericQueryResultMapper());
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle queryMap = Collections.unmodifiableMap(initializeQueryMap());
1e7cf46779cd3edd805fffdd3e5c3c8f5d36a75cBrendan Mmiller queries.setConfiguredQueries(queriesConfig, commandsConfig, queryMap);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // TODO: Consider taking into account DB meta-data rather than just configuration
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle //DatabaseMetaData metadata = connection.getMetaData();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle //boolean isBatchingSupported = metadata.supportsBatchUpdates();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle //if (!isBatchingSupported) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // maxBatchSize = 1;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.info("JDBC statement batching enabled, maximum batch size {}", this.maxBatchSize);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.info("JDBC statement batching disabled.");
5848ca5e742e748dfd18fc53d810fa017949f809Jim Mitchener * Get the length of the searchable index.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle protected Map<QueryDefinition, String> initializeQueryMap() {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle Map<QueryDefinition, String> result = new EnumMap<QueryDefinition, String>(QueryDefinition.class);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle String typeTable = dbSchemaName == null ? "objecttypes" : dbSchemaName + ".objecttypes";
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle String mainTable = dbSchemaName == null ? mainTableName : dbSchemaName + "." + mainTableName;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle String propertyTable = dbSchemaName == null ? propTableName : dbSchemaName + "." + propTableName;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // objecttypes table
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.CREATETYPEQUERYSTR, "INSERT INTO " + typeTable + " (objecttype) VALUES (?)");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.READTYPEQUERYSTR, "SELECT id FROM " + typeTable + " objtype WHERE objtype.objecttype = ?");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Main object table
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.READFORUPDATEQUERYSTR, "SELECT obj.* FROM " + mainTable + " obj INNER JOIN " + typeTable + " objtype ON obj.objecttypes_id = objtype.id AND objtype.objecttype = ? WHERE obj.objectid = ? FOR UPDATE");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.READQUERYSTR, "SELECT obj.rev, obj.fullobject FROM " + typeTable + " objtype, " + mainTable + " obj WHERE obj.objecttypes_id = objtype.id AND objtype.objecttype = ? AND obj.objectid = ?");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.CREATEQUERYSTR, "INSERT INTO " + mainTable + " (objecttypes_id, objectid, rev, fullobject) VALUES (?,?,?,?)");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.UPDATEQUERYSTR, "UPDATE " + mainTable + " obj SET obj.objectid = ?, obj.rev = ?, obj.fullobject = ? WHERE obj.id = ?");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.DELETEQUERYSTR, "DELETE obj FROM " + mainTable + " obj INNER JOIN " + typeTable + " objtype ON obj.objecttypes_id = objtype.id AND objtype.objecttype = ? WHERE obj.objectid = ? AND obj.rev = ?");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle /* DB2 Script
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle deleteQueryStr = "DELETE FROM " + dbSchemaName + "." + mainTableName + " obj WHERE EXISTS (SELECT 1 FROM " + dbSchemaName + ".objecttypes objtype WHERE obj.objecttypes_id = objtype.id AND objtype.objecttype = ?) AND obj.objectid = ? AND obj.rev = ?";
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Object properties table
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.PROPCREATEQUERYSTR, "INSERT INTO " + propertyTable + " ( " + mainTableName + "_id, propkey, proptype, propvalue) VALUES (?,?,?,?)");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.PROPDELETEQUERYSTR, "DELETE prop FROM " + propertyTable + " prop INNER JOIN " + mainTable + " obj ON prop." + mainTableName + "_id = obj.id INNER JOIN " + typeTable + " objtype ON obj.objecttypes_id = objtype.id WHERE objtype.objecttype = ? AND obj.objectid = ?");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Default object queries
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle String tableVariable = dbSchemaName == null ? "${_mainTable}" : "${_dbSchema}.${_mainTable}";
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle result.put(QueryDefinition.QUERYALLIDS, "SELECT obj.objectid FROM " + tableVariable + " obj INNER JOIN " + typeTable + " objtype ON obj.objecttypes_id = objtype.id WHERE objtype.objecttype = ${_resource}");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle /* (non-Javadoc)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @see org.forgerock.openidm.repo.jdbc.impl.TableHandler#read(java.lang.String, java.lang.String, java.lang.String, java.sql.Connection)
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchener public ResourceResponse read(String fullId, String type, String localId, Connection connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throws ResourceException, SQLException, IOException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle readStatement = getPreparedStatement(connection, QueryDefinition.READQUERYSTR);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Populating prepared statement {} for {}", readStatement, fullId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle resultMap = mapper.readValue(objString, typeRef);
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.debug(" full id: {}, rev: {}, obj {}", fullId, rev, resultMap);
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchener return newResourceResponse(localId, rev, new JsonValue(resultMap));
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw ResourceException.getException(ResourceException.NOT_FOUND,
a1d206a2a22b5cde9b00633ea4472ae0b144d695Brendan Mmiller "Object " + fullId + " not found in " + type);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle /* (non-Javadoc)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @see org.forgerock.openidm.repo.jdbc.impl.TableHandler#create(java.lang.String, java.lang.String, java.lang.String, java.util.Map, java.sql.Connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public void create(String fullId, String type, String localId, Map<String, Object> obj, Connection connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throws SQLException, IOException, InternalServerErrorException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle long typeId = getTypeId(type, connection); // Note this call can commit and start a new transaction in some cases
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle createStatement = queries.getPreparedStatement(connection, queryMap.get(QueryDefinition.CREATEQUERYSTR), true);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle obj.put("_id", localId); // Save the id in the object
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle obj.put("_rev", rev); // Save the rev in the object, and return the changed rev from the create.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle String objString = mapper.writeValueAsString(obj);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Populating statement {} with params {}, {}, {}, {}",
10ce1c22b1cd83482f0127254fe8a27610bb25f5Brendan Miller queryMap.get(QueryDefinition.CREATEQUERYSTR), typeId, localId, rev, objString);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle ResultSet keys = createStatement.getGeneratedKeys();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new InternalServerErrorException("Object creation for " + fullId + " failed to retrieve an assigned ID from the DB.");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Created object for id {} with rev {}", fullId, rev);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle writeValueProperties(fullId, dbId, localId, jv, connection);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Writes all properties of a given resource to the properties table and links them to the main table record.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param fullId the full URI of the resource the belongs to
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param dbId the generated identifier to link the properties table with the main table (foreign key)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param localId the local identifier of the resource these properties belong to
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param value the JSON value with the properties to write
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param connection the DB connection
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @throws SQLException if the insert failed
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle void writeValueProperties(String fullId, long dbId, String localId, JsonValue value, Connection connection) throws SQLException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle PreparedStatement propCreateStatement = getPreparedStatement(connection, QueryDefinition.PROPCREATEQUERYSTR);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle batchingCount = writeValueProperties(fullId, dbId, localId, value, connection, propCreateStatement, batchingCount);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int[] numUpdates = propCreateStatement.executeBatch();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Batch update of objectproperties updated: {}", numUpdates);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Writing batch of objectproperties, updated: {}", Arrays.asList(numUpdates));
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Internal recursive function to add/write properties.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * If batching is enabled, prepared statements are added to the batch and only executed if they hit the max limit.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * After completion returns the number of properties that have only been added to the batch but not yet executed.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * The caller is responsible for executing the batch on remaining items when it deems the batch complete.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * If batching is not enabled, prepared statements are immediately executed.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param fullId the full URI of the resource the belongs to
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param dbId the generated identifier to link the properties table with the main table (foreign key)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param localId the local identifier of the resource these properties belong to
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param value the JSON value with the properties to write
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param connection the DB connection
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param propCreateStatement the prepared properties insert statement
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param batchingCount the current number of statements that have been batched and not yet executed on the prepared statement
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @return status of the current batchingCount, i.e. how many statements are not yet executed in the PreparedStatement
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @throws SQLException if the insert failed
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle private int writeValueProperties(String fullId, long dbId, String localId, JsonValue value, Connection connection,
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle PreparedStatement propCreateStatement, int batchingCount) throws SQLException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle batchingCount = writeValueProperties(fullId, dbId, localId, entry, connection, propCreateStatement, batchingCount);
5848ca5e742e748dfd18fc53d810fa017949f809Jim Mitchener propvalue = StringUtils.left(val.toString(), getSearchableLength());
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle proptype = entry.getObject().getClass().getName(); // TODO: proper type info
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Populating statement {} with params {}, {}, {}, {}, {}",
10ce1c22b1cd83482f0127254fe8a27610bb25f5Brendan Miller queryMap.get(QueryDefinition.PROPCREATEQUERYSTR), dbId, localId, propkey, proptype, propvalue);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Executing: {}", propCreateStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int numUpdate = propCreateStatement.executeUpdate();
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.trace("Inserting objectproperty id: {} propkey: {} proptype: {}, propvalue: {}", fullId, propkey, proptype, propvalue);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle if (enableBatching && batchingCount >= maxBatchSize) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int[] numUpdates = propCreateStatement.executeBatch();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Batch limit reached, update of objectproperties updated: {}", Arrays.asList(numUpdates));
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @inheritDoc
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public boolean isErrorType(SQLException ex, ErrorType errorType) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle return sqlExceptionHandler.isErrorType(ex, errorType);
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @inheritDoc
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public boolean isRetryable(SQLException ex, Connection connection) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle return sqlExceptionHandler.isRetryable(ex, connection);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Ensure type is in objecttypes table and get its assigned id
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Callers should note that this may commit a transaction and start a new one if a new type gets added
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle long getTypeId(String type, Connection connection) throws SQLException, InternalServerErrorException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle connection.setAutoCommit(true); // Commit the new type right away, and have no transaction isolation for read
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Rather than relying on DB specific ignore if exists functionality handle it here
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Could extend this in the future to more explicitly check for duplicate key error codes, but these again can be DB specific
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new InternalServerErrorException("Failed to populate and look up objecttypes table, no id could be retrieved for " + type, detectedEx);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle connection.setAutoCommit(false); // Start another transaction
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param type the object type URI
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param connection the DB connection
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @return the typeId for the given type if exists, or -1 if does not exist
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @throws java.sql.SQLException
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle long readTypeId(String type, Connection connection) throws SQLException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle readTypeStatement = getPreparedStatement(connection, QueryDefinition.READTYPEQUERYSTR);
10ce1c22b1cd83482f0127254fe8a27610bb25f5Brendan Miller logger.trace("Populating prepared statement {} for {}",
10ce1c22b1cd83482f0127254fe8a27610bb25f5Brendan Miller queryMap.get(QueryDefinition.READTYPEQUERYSTR), type);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Executing: {}", readTypeStatement);
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.debug("Type: {}, id: {}", type, typeId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param type the object type URI
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param connection the DB connection
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @return true if a type was inserted
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @throws SQLException if the insert failed (e.g. concurrent insert by another thread)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle boolean createTypeId(String type, Connection connection) throws SQLException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle PreparedStatement createTypeStatement = getPreparedStatement(connection, QueryDefinition.CREATETYPEQUERYSTR);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Executing: {}", createTypeStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Reads an object with for update locking applied
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Note: statement associated with the returned resultset
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * is not closed upon return.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * Aside from taking care to close the resultset it also is
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * the responsibility of the caller to close the associated
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * statement. Although the specification specifies that drivers/pools
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * should close the statement automatically, not all do this reliably.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param fullId qualified id of component type and id
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param type the component type
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param localId the id of the object within the component type
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @param connection the connection to use
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @return the row for the requested object, selected FOR UPDATE
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @throws NotFoundException if the requested object was not found in the DB
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @throws java.sql.SQLException for general DB issues
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public ResultSet readForUpdate(String fullId, String type, String localId, Connection connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle PreparedStatement readForUpdateStatement = null;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle readForUpdateStatement = getPreparedStatement(connection, QueryDefinition.READFORUPDATEQUERYSTR);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Populating prepared statement {} for {}", readForUpdateStatement, fullId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Executing: {}", readForUpdateStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Read for update full id: {}", fullId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle CleanupHelper.loggedClose(readForUpdateStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new NotFoundException("Object " + fullId + " not found in " + type);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle CleanupHelper.loggedClose(readForUpdateStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle /* (non-Javadoc)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @see org.forgerock.openidm.repo.jdbc.impl.TableHandler#update(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Map, java.sql.Connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public void update(String fullId, String type, String localId, String rev, Map<String, Object> obj, Connection connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throws SQLException, IOException, PreconditionFailedException, NotFoundException, InternalServerErrorException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle obj.put("_rev", newRev); // Save the rev in the object, and return the changed rev from the create.
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle rs = readForUpdate(fullId, type, localId, connection);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle long objectTypeDbId = rs.getLong("objecttypes_id");
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.debug("Update existing object {} rev: {} db id: {}, object type db id: {}", fullId, existingRev, dbId, objectTypeDbId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new PreconditionFailedException("Update rejected as current Object revision " + existingRev + " is different than expected by caller (" + rev + "), the object has changed since retrieval.");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle updateStatement = getPreparedStatement(connection, QueryDefinition.UPDATEQUERYSTR);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle deletePropStatement = getPreparedStatement(connection, QueryDefinition.PROPDELETEQUERYSTR);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Support changing object identifier
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle if (newLocalId != null && !localId.equals(newLocalId)) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Object identifier is changing from " + localId + " to " + newLocalId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle newLocalId = localId; // If it hasn't changed, use the existing ID
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle obj.put("_id", newLocalId); // Ensure the ID is saved in the object
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle String objString = mapper.writeValueAsString(obj);
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.trace("Populating prepared statement {} for {} {} {} {} {}", updateStatement, fullId, newLocalId, newRev, objString, dbId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Update statement: {}", updateStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int updateCount = updateStatement.executeUpdate();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Updated rows: {} for {}", updateCount, fullId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new InternalServerErrorException("Update execution did not result in updating 1 row as expected. Updated rows: " + updateCount);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // TODO: only update what changed?
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.trace("Populating prepared statement {} for {} {} {}", deletePropStatement, fullId, type, localId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Update properties del statement: {}", deletePropStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int deleteCount = deletePropStatement.executeUpdate();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Deleted child rows: {} for: {}", deleteCount, fullId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle writeValueProperties(fullId, dbId, localId, jv, connection);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Ensure associated statement also is closed
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @see org.forgerock.openidm.repo.jdbc.impl.GenericTableHandler#delete(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.sql.Connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public void delete(String fullId, String type, String localId, String rev, Connection connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throws PreconditionFailedException, InternalServerErrorException, NotFoundException, SQLException, IOException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // First check if the revision matches and select it for UPDATE
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle existing = readForUpdate(fullId, type, localId, connection);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new NotFoundException("Object does not exist for delete on: " + fullId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle if (!"*".equals(rev) && !rev.equals(existingRev)) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new PreconditionFailedException("Delete rejected as current Object revision " + existingRev + " is different than "
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle + "expected by caller " + rev + ", the object has changed since retrieval.");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Proceed with the valid delete
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle deleteStatement = getPreparedStatement(connection, QueryDefinition.DELETEQUERYSTR);
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller logger.trace("Populating prepared statement {} for {} {} {} {}", deleteStatement, fullId, type, localId, rev);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Rely on ON DELETE CASCADE for connected object properties to be deleted
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("Delete statement: {}", deleteStatement);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle int deletedRows = deleteStatement.executeUpdate();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Deleted {} rows for id : {} {}", deletedRows, localId);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle throw new InternalServerErrorException("Deleting object for " + fullId + " failed, DB reported " + deletedRows + " rows deleted");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.debug("delete for id succeeded: {} revision: {}", localId, rev);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Ensure associated statement also is closed
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle Statement existingStatement = existing.getStatement();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle /* (non-Javadoc)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle * @see org.forgerock.openidm.repo.jdbc.impl.TableHandler#delete(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.sql.Connection)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public List<Map<String, Object>> query(String type, Map<String, Object> params, Connection connection)
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller public Integer command(String type, Map<String, Object> params, Connection connection) throws SQLException, ResourceException {
79d12e9411155c9e79c08df9bb6412b2862e6064Brendan Mmiller return queries.command(type, params, connection);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle return "Generic handler mapped to [" + mainTableName + ", " + propTableName + "]";
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle protected PreparedStatement getPreparedStatement(Connection connection, QueryDefinition queryDefinition) throws SQLException {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle return queries.getPreparedStatement(connection, queryMap.get(queryDefinition));
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * Render and SQL SELECT statement with placeholders for the given query filter.
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @param filter the query filter
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @param replacementTokens a map to store any replacement tokens
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @param params a map containing query parameters
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @return an SQL SELECT statement
af9b081b11f86e7fddd67382b1db65dbd5650332Jim Mitchener public String renderQueryFilter(QueryFilter<JsonPointer> filter, Map<String, Object> replacementTokens, Map<String, Object> params) {
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller final int offsetParam = Integer.parseInt((String) params.get(PAGED_RESULTS_OFFSET));
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller final int pageSizeParam = Integer.parseInt((String) params.get(PAGE_SIZE));
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller // "SELECT obj.* FROM mainTable obj..."
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller // join objecttypes to fix OPENIDM-2773
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller .join("${_dbSchema}.objecttypes", "objecttypes")
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller .on(where("obj.objecttypes_id = objecttypes.id")
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller // construct where clause by visiting filter
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller .where(filter.accept(new GenericSQLQueryFilterVisitor(SEARCHABLE_LENGTH, builder), replacementTokens));
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller // other half of OPENIDM-2773 fix
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller replacementTokens.put("otype", params.get("_resource"));
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller // JsonValue-cheat to avoid an unchecked cast
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller final List<SortKey> sortKeys = new JsonValue(params).get(SORT_KEYS).asList(SortKey.class);
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienle // Check for sort keys and build up order-by syntax
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller prepareSortKeyStatements(builder, sortKeys, replacementTokens);
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienle * Loops through sort keys constructing the inner join and key statements.
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @param builder the SQL builder
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @param sortKeys a {@link java.util.List} of sort keys
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller * @param replacementTokens a {@link java.util.Map} containing replacement tokens for the {@link java.sql.PreparedStatement}
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller protected void prepareSortKeyStatements(SQLBuilder builder, List<SortKey> sortKeys, Map<String, Object> replacementTokens) {
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller builder.join("${_dbSchema}.${_propTable}", tableAlias)
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller .on(where(tableAlias + ".${_mainTable}_id = obj.id").and(tableAlias + ".propkey = ${" + tokenName + "}"))
5c6fc9459842796234027c8bb8f58886d69ebc8fBrendan Mmiller .orderBy(tableAlias + ".propvalue", sortKey.isAscendingOrder());
a32c32954ffe171a0437ff1bc1287e9086f0bd26Chad Kienle replacementTokens.put(tokenName, sortKey.getField().toString());
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienleclass GenericQueryResultMapper implements QueryResultMapper {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle final static Logger logger = LoggerFactory.getLogger(GenericQueryResultMapper.class);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Jackson parser
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Type information for the Jackson parser
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle TypeReference<LinkedHashMap<String,Object>> typeRef = new TypeReference<LinkedHashMap<String,Object>>() {};
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public List<Map<String, Object>> mapQueryToObject(ResultSet rs, String queryId, String type, Map<String, Object> params, TableQueries tableQueries)
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle ResultSetMetaData rsMetaData = rs.getMetaData();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle boolean hasFullObject = tableQueries.hasColumn(rsMetaData, "fullobject");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle boolean hasId = false;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle boolean hasRev = false;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle boolean hasPropKey = false;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle boolean hasPropValue = false;
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener boolean hasTotal = false;
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle hasId = tableQueries.hasColumn(rsMetaData, "objectid");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle hasRev = tableQueries.hasColumn(rsMetaData, "rev");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle hasPropKey = tableQueries.hasColumn(rsMetaData, "propkey");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle hasPropValue = tableQueries.hasColumn(rsMetaData, "propvalue");
117941f80637bf0fe66cbb3a444430ef88f629a7Jim Mitchener hasTotal = tableQueries.hasColumn(rsMetaData, "total");
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle Map<String, Object> obj = mapper.readValue(objString, typeRef);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // TODO: remove data logging
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle logger.trace("Query result for queryId: {} type: {} converted obj: {}", new Object[] {queryId, type, obj});
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle Map<String, Object> obj = new HashMap<String, Object>();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle // Results from query on individual searchable property
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public boolean isSearchable(JsonPointer propPointer) {
e441210fb6e4bab95f05f24644e4d2ed152efd8cAndi Egloff // More specific configuration takes precedence
e441210fb6e4bab95f05f24644e4d2ed152efd8cAndi Egloff while (!propPointer.isEmpty() && explicit == null) {
e441210fb6e4bab95f05f24644e4d2ed152efd8cAndi Egloff explicit = properties.explicitlySearchable.get(propPointer);
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff * @return Approximation on whether this may have searchable properties
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff * It is only an approximation as we do not have an exhaustive list of possible properties
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff * to consider against a default setting of searchable.
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff public boolean hasPossibleSearchableProperties() {
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff return ((searchableDefault) ? true : properties.explicitSearchableProperties);
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public static GenericTableConfig parse(JsonValue tableConfig) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle GenericTableConfig cfg = new GenericTableConfig();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle cfg.mainTableName = tableConfig.get("mainTable").required().asString();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle cfg.propertiesTableName = tableConfig.get("propertiesTable").required().asString();
1e7cf46779cd3edd805fffdd3e5c3c8f5d36a75cBrendan Mmiller cfg.searchableDefault = tableConfig.get("searchableDefault").defaultTo(Boolean.TRUE).asBoolean();
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle cfg.properties = GenericPropertiesConfig.parse(tableConfig.get("properties"));
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public Map<JsonPointer, Boolean> explicitlySearchable = new HashMap<JsonPointer, Boolean>();
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff // Whether there are any properties explicitly set to searchable true
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle public static GenericPropertiesConfig parse(JsonValue propsConfig) {
37f9df7d5b474a12668813f98992dceb7c7feacbChad Kienle GenericPropertiesConfig cfg = new GenericPropertiesConfig();
9f1e7c4b37140496d6fb5ad0a3ae507e4f9db37cAndi Egloff boolean propSearchable = detail.get("searchable").asBoolean();