/* * ==================== * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of the Common Development * and Distribution License("CDDL") (the "License"). You may not use this file * except in compliance with the License. * * You can obtain a copy of the License at * http://opensource.org/licenses/cddl1.php * See the License for the specific language governing permissions and limitations * under the License. * * When distributing the Covered Code, include this CDDL Header Notice in each file * and include the License file at http://opensource.org/licenses/cddl1.php. * If applicable, add the following below this CDDL Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * ==================== * Portions Copyrighted 2014 ForgeRock AS. */ package org.identityconnectors.framework.common.objects; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.identityconnectors.common.Assertions; import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.api.operations.AuthenticationApiOp; import org.identityconnectors.framework.api.operations.CreateApiOp; import org.identityconnectors.framework.api.operations.DeleteApiOp; import org.identityconnectors.framework.api.operations.GetApiOp; import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp; import org.identityconnectors.framework.api.operations.SchemaApiOp; import org.identityconnectors.framework.api.operations.ScriptOnConnectorApiOp; import org.identityconnectors.framework.api.operations.ScriptOnResourceApiOp; import org.identityconnectors.framework.api.operations.SearchApiOp; import org.identityconnectors.framework.api.operations.SyncApiOp; import org.identityconnectors.framework.api.operations.TestApiOp; import org.identityconnectors.framework.api.operations.UpdateApiOp; import org.identityconnectors.framework.api.operations.ValidateApiOp; import org.identityconnectors.framework.common.FrameworkUtil; import org.identityconnectors.framework.spi.Connector; import org.identityconnectors.framework.spi.operations.SPIOperation; import org.identityconnectors.framework.spi.operations.SchemaOp; import org.identityconnectors.framework.spi.operations.ScriptOnConnectorOp; import org.identityconnectors.framework.spi.operations.ScriptOnResourceOp; import org.identityconnectors.framework.spi.operations.TestOp; /** * Simple builder class to help facilitate creating a {@link Schema} object. * * @author Will Droste * @since 1.0 */ public final class SchemaBuilder { private final Set<ObjectClassInfo> declaredObjectClasses = new HashSet<ObjectClassInfo>(); private final Set<OperationOptionInfo> declaredOperationOptions = new HashSet<OperationOptionInfo>(); private final Map<Class<? extends APIOperation>, Set<ObjectClassInfo>> supportedObjectClassesByOperation = new HashMap<Class<? extends APIOperation>, Set<ObjectClassInfo>>(); private final Map<Class<? extends APIOperation>, Set<OperationOptionInfo>> supportedOptionsByOperation = new HashMap<Class<? extends APIOperation>, Set<OperationOptionInfo>>(); private final Set<Class<? extends APIOperation>> defaultSupportedOperations; /** * Creates a SchemaBuilder for the given connector class * * @param connectorClass * The connector for which we are building the schema. */ public SchemaBuilder(Class<? extends Connector> connectorClass) { Assertions.nullCheck(connectorClass, "connectorClass"); this.defaultSupportedOperations = FrameworkUtil .getDefaultSupportedOperations(connectorClass); } private boolean objectClassOperation(Class<? extends APIOperation> op) { if (AuthenticationApiOp.class.equals(op) || CreateApiOp.class.equals(op) || DeleteApiOp.class.equals(op) || GetApiOp.class.equals(op) || ResolveUsernameApiOp.class.equals(op) || SearchApiOp.class.equals(op) || SyncApiOp.class.equals(op) || UpdateApiOp.class.equals(op)) { return true; } return false; } private boolean operationOptionOperation(Class<? extends APIOperation> op) { if (AuthenticationApiOp.class.equals(op) || CreateApiOp.class.equals(op) || DeleteApiOp.class.equals(op) || GetApiOp.class.equals(op) || ResolveUsernameApiOp.class.equals(op) || ScriptOnConnectorApiOp.class.equals(op) || ScriptOnResourceApiOp.class.equals(op) || SearchApiOp.class.equals(op) || SyncApiOp.class.equals(op) || UpdateApiOp.class.equals(op)) { return true; } return false; } /** * Adds another ObjectClassInfo to the schema. * * Also, adds this to the set of supported classes for every operation * defined by the Connector. * * @param info * @throws IllegalStateException * If already defined */ public void defineObjectClass(ObjectClassInfo info) { Assertions.nullCheck(info, "info"); if (declaredObjectClasses.contains(info)) { throw new IllegalStateException("ObjectClass already defined: " + info.getType()); } declaredObjectClasses.add(info); for (Class<? extends APIOperation> op : defaultSupportedOperations) { if (objectClassOperation(op)) { Set<ObjectClassInfo> oclasses = supportedObjectClassesByOperation.get(op); if (oclasses == null) { oclasses = new HashSet<ObjectClassInfo>(); supportedObjectClassesByOperation.put(op, oclasses); } oclasses.add(info); } } } /** * Adds another ObjectClassInfo to the schema. * * Also, adds this to the set of supported classes for every operation * defined by the Connector. * * @param objectClassInfo * @param operations * The SPI operation which use supports this * {@code objectClassInfo} * * @throws IllegalStateException * If already defined */ public void defineObjectClass(ObjectClassInfo objectClassInfo, Class<? extends SPIOperation>... operations) { if (operations.length > 0) { Assertions.nullCheck(objectClassInfo, "objectClassInfo"); if (declaredObjectClasses.contains(objectClassInfo)) { throw new IllegalStateException("ObjectClass already defined: " + objectClassInfo.getType()); } declaredObjectClasses.add(objectClassInfo); for (Class<? extends SPIOperation> spi : operations) { if (SchemaOp.class.equals(spi) || ScriptOnConnectorOp.class.equals(spi) || ScriptOnResourceOp.class.equals(spi) || TestOp.class.equals(spi)) { continue; } Set<Class<? extends APIOperation>> apiOperations = FrameworkUtil.spi2apis(spi); apiOperations.retainAll(defaultSupportedOperations); for (Class<? extends APIOperation> api : apiOperations) { if (objectClassOperation(api)) { Set<ObjectClassInfo> oclasses = supportedObjectClassesByOperation.get(api); if (oclasses == null) { oclasses = new HashSet<ObjectClassInfo>(); supportedObjectClassesByOperation.put(api, oclasses); } oclasses.add(objectClassInfo); } } } } else { defineObjectClass(objectClassInfo); } } /** * Adds another OperationOptionInfo to the schema. Also, adds this to the * set of supported options for every operation defined by the Connector. */ public void defineOperationOption(OperationOptionInfo info) { Assertions.nullCheck(info, "info"); if (declaredOperationOptions.contains(info)) { throw new IllegalStateException("OperationOption already defined: " + info.getName()); } declaredOperationOptions.add(info); for (Class<? extends APIOperation> op : defaultSupportedOperations) { if (operationOptionOperation(op)) { Set<OperationOptionInfo> oclasses = supportedOptionsByOperation.get(op); if (oclasses == null) { oclasses = new HashSet<OperationOptionInfo>(); supportedOptionsByOperation.put(op, oclasses); } oclasses.add(info); } } } /** * Adds another OperationOptionInfo to the schema. Also, adds this to the * set of supported options for operation defined. * * @param operationOptionInfo * @param operations * * @throws IllegalStateException * If already defined */ public void defineOperationOption(OperationOptionInfo operationOptionInfo, Class<? extends SPIOperation>... operations) { if (operations.length > 0) { Assertions.nullCheck(operationOptionInfo, "info"); if (declaredOperationOptions.contains(operationOptionInfo)) { throw new IllegalStateException("OperationOption already defined: " + operationOptionInfo.getName()); } declaredOperationOptions.add(operationOptionInfo); for (Class<? extends SPIOperation> spi : operations) { if (SchemaOp.class.equals(spi) || TestOp.class.equals(spi)) { continue; } Set<Class<? extends APIOperation>> apiOperations = FrameworkUtil.spi2apis(spi); apiOperations.retainAll(defaultSupportedOperations); for (Class<? extends APIOperation> api : apiOperations) { if (operationOptionOperation(api)) { Set<OperationOptionInfo> oclasses = supportedOptionsByOperation.get(api); if (oclasses == null) { oclasses = new HashSet<OperationOptionInfo>(); supportedOptionsByOperation.put(api, oclasses); } oclasses.add(operationOptionInfo); } } } } else { defineOperationOption(operationOptionInfo); } } /** * Adds another ObjectClassInfo to the schema. * * Also, adds this to the set of supported classes for every operation * defined by the Connector. * * @throws IllegalStateException * If already defined */ public void defineObjectClass(String type, Set<AttributeInfo> attrInfo) { ObjectClassInfoBuilder bld = new ObjectClassInfoBuilder(); bld.setType(type); bld.addAllAttributeInfo(attrInfo); ObjectClassInfo obj = bld.build(); defineObjectClass(obj); } /** * Adds another OperationOptionInfo to the schema. * * Also, adds this to the set of supported options for every operation * defined by the Connector. * * @throws IllegalStateException * If already defined */ public void defineOperationOption(String optionName, Class<?> type) { OperationOptionInfoBuilder bld = new OperationOptionInfoBuilder(); bld.setName(optionName); bld.setType(type); OperationOptionInfo info = bld.build(); defineOperationOption(info); } /** * Adds the given ObjectClassInfo as a supported ObjectClass for the given * operation. * * @param op * The SPI operation * @param def * The ObjectClassInfo * @throws IllegalArgumentException * If the given ObjectClassInfo was not already defined using * {@link #defineObjectClass(ObjectClassInfo)}. */ public void addSupportedObjectClass(Class<? extends SPIOperation> op, ObjectClassInfo def) { Assertions.nullCheck(op, "op"); Assertions.nullCheck(def, "def"); Set<Class<? extends APIOperation>> apis = FrameworkUtil.spi2apis(op); apis.retainAll(defaultSupportedOperations); if (!declaredObjectClasses.contains(def)) { throw new IllegalArgumentException("ObjectClass " + def.getType() + " not defined in schema."); } for (Class<? extends APIOperation> api : apis) { if (objectClassOperation(api)) { Set<ObjectClassInfo> infos = supportedObjectClassesByOperation.get(api); if (infos == null) { throw new IllegalArgumentException("Operation " + op.getName() + " not implement by connector."); } if (infos.contains(def)) { throw new IllegalArgumentException("ObjectClass " + def.getType() + " already supported for operation " + op.getName()); } infos.add(def); } } } /** * Removes the given ObjectClassInfo as a supported ObjectClass for the * given operation. * * @param op * The SPI operation * @param def * The ObjectClassInfo * @throws IllegalArgumentException * If the given ObjectClassInfo was not already defined using * {@link #defineObjectClass(ObjectClassInfo)}. */ public void removeSupportedObjectClass(Class<? extends SPIOperation> op, ObjectClassInfo def) { Assertions.nullCheck(op, "op"); Assertions.nullCheck(def, "def"); Set<Class<? extends APIOperation>> apis = FrameworkUtil.spi2apis(op); if (!declaredObjectClasses.contains(def)) { throw new IllegalArgumentException("ObjectClass " + def.getType() + " not defined in schema."); } for (Class<? extends APIOperation> api : apis) { if (objectClassOperation(api)) { if (defaultSupportedOperations.contains(api)) { Set<ObjectClassInfo> infos = supportedObjectClassesByOperation.get(api); if (null == infos || !infos.contains(def)) { throw new IllegalArgumentException("ObjectClass " + def.getType() + " already removed for operation " + op.getName()); } infos.remove(def); } else { throw new IllegalArgumentException("Operation " + op.getName() + " not implement by connector."); } } } } /** * Adds the given OperationOptionInfo as a supported option for the given * operation. * * @param op * The SPI operation * @param def * The OperationOptionInfo * @throws IllegalArgumentException * If the given OperationOptionInfo was not already defined * using {@link #defineOperationOption(OperationOptionInfo)}. */ public void addSupportedOperationOption(Class<? extends SPIOperation> op, OperationOptionInfo def) { Assertions.nullCheck(op, "op"); Assertions.nullCheck(def, "def"); Set<Class<? extends APIOperation>> apis = FrameworkUtil.spi2apis(op); apis.retainAll(defaultSupportedOperations); if (!declaredOperationOptions.contains(def)) { throw new IllegalArgumentException("OperationOption " + def.getName() + " not defined in schema."); } for (Class<? extends APIOperation> api : apis) { if (operationOptionOperation(api)) { Set<OperationOptionInfo> infos = supportedOptionsByOperation.get(api); if (infos == null) { throw new IllegalArgumentException("Operation " + op.getName() + " not implement by connector."); } if (infos.contains(def)) { throw new IllegalArgumentException("OperationOption " + def.getName() + " already supported for operation " + op.getName()); } infos.add(def); } } } /** * Removes the given OperationOptionInfo as a supported option for the given * operation. * * @param op * The SPI operation * @param def * The OperationOptionInfo * @throws IllegalArgumentException * If the given OperationOptionInfo was not already defined * using {@link #defineOperationOption(OperationOptionInfo)}. */ public void removeSupportedOperationOption(Class<? extends SPIOperation> op, OperationOptionInfo def) { Assertions.nullCheck(op, "op"); Assertions.nullCheck(def, "def"); Set<Class<? extends APIOperation>> apis = FrameworkUtil.spi2apis(op); if (!declaredOperationOptions.contains(def)) { throw new IllegalArgumentException("OperationOption " + def.getName() + " not defined in schema."); } for (Class<? extends APIOperation> api : apis) { if (operationOptionOperation(api)) { if (defaultSupportedOperations.contains(api)) { Set<OperationOptionInfo> infos = supportedOptionsByOperation.get(api); if (null == infos || !infos.contains(def)) { throw new IllegalArgumentException("OperationOption " + def.getName() + " already removed for operation " + op.getName()); } infos.remove(def); } else { throw new IllegalArgumentException("Operation " + op.getName() + " not implement by connector."); } } } } /** * Clears the operation-specific supported classes. * * Normally, when you add an ObjectClass, using * {@link #defineObjectClass(ObjectClassInfo)}, it is added to all * operations. You may then remove those that you need using * {@link #removeSupportedObjectClass(Class, ObjectClassInfo)}. You may * wish, as an alternative to clear everything out and instead add using * {@link #addSupportedObjectClass(Class, ObjectClassInfo)}. */ public void clearSupportedObjectClassesByOperation() { for (Set<ObjectClassInfo> values : supportedObjectClassesByOperation.values()) { values.clear(); } } /** * Clears the operation-specific supported options. * * Normally, when you add an OperationOptionInfo using * {@link #defineOperationOption(OperationOptionInfo)}, this adds the option * to all operations. You may then remove the option from any operation that * does not support the option using * {@link #removeSupportedOperationOption(Class, OperationOptionInfo)}. An * alternative approach is to clear everything out (using this method) and * then add each option to every operation that supports the option using * {@link #addSupportedOperationOption(Class, OperationOptionInfo)}. */ public void clearSupportedOptionsByOperation() { for (Set<OperationOptionInfo> values : supportedOptionsByOperation.values()) { values.clear(); } } /** * Builds the {@link Schema} object based on the {@link ObjectClassInfo}s * added so far. * * @return new Schema object based on the info provided. */ public Schema build() { if (declaredObjectClasses.isEmpty()) { throw new IllegalStateException("Must be at least one ObjectClassInfo object!"); } return new Schema(declaredObjectClasses, declaredOperationOptions, supportedObjectClassesByOperation, supportedOptionsByOperation); } }