/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2015 ForgeRock AS. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" */ package org.forgerock.openidm.managed; import javax.script.ScriptException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.forgerock.json.JsonPointer; import org.forgerock.json.JsonValue; import org.forgerock.json.JsonValueException; import org.forgerock.openidm.crypto.CryptoService; import org.forgerock.script.ScriptRegistry; import org.forgerock.util.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a managed object's schema */ public class ManagedObjectSchema { /** * Setup logging for the {@link ManagedObjectSchema}. */ private static final Logger logger = LoggerFactory.getLogger(ManagedObjectSchema.class); /** * The schema to use to validate the structure and content of the managed object. */ private final Map<JsonPointer, SchemaField> fields; /** * The schema to use to validate the structure and content of the managed object. */ private final List<JsonPointer> relationshipFields; /** * The schema to use to validate the structure and content of the managed object. */ private final Map<JsonPointer, SchemaField> hiddenByDefaultFields; public ManagedObjectSchema(JsonValue schema, ScriptRegistry scriptRegistry, CryptoService cryptoService) throws JsonValueException, ScriptException { JsonValue schemaProperties = schema.get("properties").expect(Map.class); fields = new LinkedHashMap<>(); relationshipFields = new ArrayList<>(); hiddenByDefaultFields = new LinkedHashMap<>(); if (!schemaProperties.isNull()) { for (String propertyKey : schemaProperties.keys()) { SchemaField schemaField = new SchemaField(propertyKey, schemaProperties.get(propertyKey), scriptRegistry, cryptoService); fields.put(new JsonPointer(propertyKey), schemaField); if (!schemaField.isReturnedByDefault()) { logger.debug("Field {} is not returned by default", propertyKey); hiddenByDefaultFields.put(new JsonPointer(propertyKey), schemaField); } if (schemaField.isRelationship()) { relationshipFields.add(new JsonPointer(propertyKey)); } } } } /** * Returns a {@link Map} containing the schema field names and their corresponding {@link SchemaField} object. * * @return a map of the schema fields. */ public Map<JsonPointer, SchemaField> getFields() { return fields; } /** * Returns a {@link SchemaField} representing a schema field corresponding to the supplied field name. * * @param field a {@link JsonPointer} representing a field name. * @return a {@link SchemaField} representing a schema field. */ public SchemaField getField(JsonPointer field) { return fields.get(field); } /** * Returns a boolean indicating if the supplied {@link JsonPointer} is a field declared in the schema. * * @return true if the field is declared in the schema, false otherwise. */ public boolean hasField(JsonPointer field) { return fields.containsKey(field); } /** * Returns a {@link List} of the relationship fields. * * @return a list of relationship fields */ public List<JsonPointer> getRelationshipFields() { return relationshipFields; } /** * Returns a {@link JsonValue} object representing a map of the fields that are hidden by default. * All relationship and virtual fields will be hidden by default unless the returnByDefault flag is set to true. * * @return a map of fields that are hidden by default. */ public Map<JsonPointer, SchemaField> getHiddenByDefaultFields() { return hiddenByDefaultFields; } /** * Determines if the supplied {@link JsonPointer} represents a resource expanded field name or a relationship * field, and if so, returns a {@link Pair} representing the relationship field's name on the left and the * expanded resource's field name on the right. * * @param field a {@link JsonPointer} representing a field name. * @return a {@link Pair} representing the relationship field's name on the left and the * expanded resource's field name on the right, or null if the field is not an expanded field. */ public Pair<JsonPointer, JsonPointer> getResourceExpansionField(JsonPointer field) { for (JsonPointer relationshipField : relationshipFields) { JsonPointer fieldToMatch = field; boolean matches = true; for (String relationshipFieldToken : relationshipField.toArray()) { if (!relationshipFieldToken.equals(fieldToMatch.get(0))) { matches = false; break; } fieldToMatch = field.relativePointer(); } if (matches) { if (fields.get(relationshipField).isArray()) { // The field is an Array, check if it is followed by an "*", to indicate field expansion if (fieldToMatch.get(0).equals("*")) { if (fieldToMatch.toArray().length > 1) { // Return the remaining field name return Pair.of(relationshipField, fieldToMatch.relativePointer()); } else if (fieldToMatch.toArray().length == 1) { // Return all fields "*" return Pair.of(relationshipField, fieldToMatch); } } else { // No expansion return null; } } else { // The field is not an array, check if it has any remaining field name to indicate field expansion if (fieldToMatch.toArray().length > 0) { return Pair.of(relationshipField, fieldToMatch); } else { // No expansion return null; } } } } return null; } /** * Returns true if the fieldIndexPointer refers to an index of an array field: ie 'roles/0'. It would not * match on field expansions like 'roles/*/description' * * @param fieldIndexPointer the possible pointer to an index of an array field. * @return true if the fieldIndexPointer refers to an index of an array field */ public boolean hasArrayIndexedField(JsonPointer fieldIndexPointer) { return fieldIndexPointer.size() == 2 && hasField(fieldIndexPointer.parent()) && fieldIndexPointer.leaf().matches("[0-9]+") && getField(fieldIndexPointer.parent()).isArray(); } }