Java tutorial
/* * Licensed to the University Corporation for Advanced Internet Development, * Inc. (UCAID) under one or more contributor license agreements. See the * NOTICE file distributed with this work for additional information regarding * copyright ownership. The UCAID licenses this file to You under the Apache * License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.internet2.middleware.psp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.opensaml.util.resource.ResourceException; import org.opensaml.xml.util.DatatypeHelper; import org.openspml.v2.msg.OCEtoMarshallableAdapter; import org.openspml.v2.msg.spml.AddRequest; import org.openspml.v2.msg.spml.AddResponse; import org.openspml.v2.msg.spml.CapabilitiesList; import org.openspml.v2.msg.spml.Capability; import org.openspml.v2.msg.spml.CapabilityData; import org.openspml.v2.msg.spml.DeleteRequest; import org.openspml.v2.msg.spml.DeleteResponse; import org.openspml.v2.msg.spml.ErrorCode; import org.openspml.v2.msg.spml.ListTargetsRequest; import org.openspml.v2.msg.spml.ListTargetsResponse; import org.openspml.v2.msg.spml.LookupRequest; import org.openspml.v2.msg.spml.LookupResponse; import org.openspml.v2.msg.spml.Modification; import org.openspml.v2.msg.spml.ModificationMode; import org.openspml.v2.msg.spml.ModifyRequest; import org.openspml.v2.msg.spml.ModifyResponse; import org.openspml.v2.msg.spml.PSO; import org.openspml.v2.msg.spml.PSOIdentifier; import org.openspml.v2.msg.spml.Request; import org.openspml.v2.msg.spml.Response; import org.openspml.v2.msg.spml.ReturnData; import org.openspml.v2.msg.spml.Schema; import org.openspml.v2.msg.spml.SchemaEntityRef; import org.openspml.v2.msg.spml.StatusCode; import org.openspml.v2.msg.spmlbatch.OnError; import org.openspml.v2.msg.spmlref.HasReference; import org.openspml.v2.msg.spmlref.Reference; import org.openspml.v2.msg.spmlref.ReferenceDefinition; import org.openspml.v2.msg.spmlsearch.Query; import org.openspml.v2.msg.spmlsearch.Scope; import org.openspml.v2.msg.spmlsearch.SearchRequest; import org.openspml.v2.msg.spmlsearch.SearchResponse; import org.openspml.v2.profiles.DSMLProfileRegistrar; import org.openspml.v2.profiles.dsml.DSMLAttr; import org.openspml.v2.profiles.dsml.DSMLModification; import org.openspml.v2.profiles.dsml.DSMLProfileException; import org.openspml.v2.profiles.dsml.DSMLUnmarshaller; import org.openspml.v2.profiles.dsml.DSMLValue; import org.openspml.v2.profiles.dsml.EqualityMatch; import org.openspml.v2.profiles.dsml.Filter; import org.openspml.v2.profiles.spmldsml.AttributeDefinition; import org.openspml.v2.profiles.spmldsml.AttributeDefinitionReference; import org.openspml.v2.profiles.spmldsml.AttributeDefinitionReferences; import org.openspml.v2.profiles.spmldsml.DSMLSchema; import org.openspml.v2.profiles.spmldsml.ObjectClassDefinition; import org.openspml.v2.util.Spml2Exception; import org.openspml.v2.util.xml.ObjectFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import edu.internet2.middleware.psp.spml.config.Pso; import edu.internet2.middleware.psp.spml.config.PsoAttribute; import edu.internet2.middleware.psp.spml.config.PsoIdentifyingAttribute; import edu.internet2.middleware.psp.spml.config.PsoReference; import edu.internet2.middleware.psp.spml.config.PsoReferences; import edu.internet2.middleware.psp.spml.provider.BaseSpmlProvider; import edu.internet2.middleware.psp.spml.provider.SpmlProvider; import edu.internet2.middleware.psp.spml.provider.SpmlTarget; import edu.internet2.middleware.psp.spml.request.AlternateIdentifier; import edu.internet2.middleware.psp.spml.request.BulkCalcRequest; import edu.internet2.middleware.psp.spml.request.BulkCalcResponse; import edu.internet2.middleware.psp.spml.request.BulkDiffRequest; import edu.internet2.middleware.psp.spml.request.BulkDiffResponse; import edu.internet2.middleware.psp.spml.request.BulkProvisioningRequest; import edu.internet2.middleware.psp.spml.request.BulkSyncRequest; import edu.internet2.middleware.psp.spml.request.BulkSyncResponse; import edu.internet2.middleware.psp.spml.request.CalcRequest; import edu.internet2.middleware.psp.spml.request.CalcResponse; import edu.internet2.middleware.psp.spml.request.DiffRequest; import edu.internet2.middleware.psp.spml.request.DiffResponse; import edu.internet2.middleware.psp.spml.request.ProvisioningRequest; import edu.internet2.middleware.psp.spml.request.ProvisioningResponse; import edu.internet2.middleware.psp.spml.request.PspMarshallableCreator; import edu.internet2.middleware.psp.spml.request.SearchRequestWithQueryClauseNamespaces; import edu.internet2.middleware.psp.spml.request.SyncRequest; import edu.internet2.middleware.psp.spml.request.SyncResponse; import edu.internet2.middleware.psp.spml.request.SynchronizedResponse; import edu.internet2.middleware.psp.util.AttributeModifier; import edu.internet2.middleware.psp.util.MDCHelper; import edu.internet2.middleware.psp.util.PSPUtil; import edu.internet2.middleware.shibboleth.common.attribute.AttributeAuthority; import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException; import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute; import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext; import edu.internet2.middleware.shibboleth.common.service.ServiceException; /** * Represents an (incomplete) spmlv2 provisioning service provider supporting calc, diff, and sync operations whose data * is calculated by a shibboleth attribute resolver. */ public class Psp extends BaseSpmlProvider implements SpmlProvider { /** Logger. */ private static final Logger LOG = LoggerFactory.getLogger(Psp.class); /** Required bootstrap configuration files. */ private static String[] CONFIG_FILES = { "psp-internal.xml", "psp-services.xml", }; /** Configuration xml element name. */ public static final String BEAN_NAME = "psp"; /** The Shibboleth attribute authority. */ private AttributeAuthority attributeAuthority; /** Spring identifier. */ private String id; /** Map whose keys are target IDs and values are provisioned object definitions. */ private Map<String, List<Pso>> objects = Collections.EMPTY_MAP; /** Runtime configuration. */ private PspOptions pspOptions; /** Map whose keys are target IDs and values are targets. */ private Map<String, SpmlTarget> targets = Collections.EMPTY_MAP; /** Constructor. */ public Psp() { } /** * Get a {@link Psp} with default options. * * @return the provisioning service provider * @throws ResourceException if a configuration error occurs */ public static Psp getPSP() throws ResourceException { return getPSP(new PspOptions()); } /** * Get a {@link Psp}. * * @param pspOptions the psp options * @return the provisioning service provider * @throws ResourceException if a configuration error occurs */ public static Psp getPSP(PspOptions pspOptions) throws ResourceException { String confDir = pspOptions != null ? pspOptions.getConfDir() : null; LOG.info("Loading psp from configuration directory '{}'", confDir); ApplicationContext context = PSPUtil.createSpringContext(PSPUtil.getResources(confDir, CONFIG_FILES)); Psp psp = (Psp) context.getBean(BEAN_NAME); psp.setPspOptions(pspOptions); return psp; } /** * Return an spmlv2 add request. * * @param pso the provisioning service object * @param returnData the return data * @return the spmlv2 add request */ public AddRequest createAddRequest(PSO pso, ReturnData returnData) { AddRequest addRequest = new AddRequest(); addRequest.setRequestID(PSPUtil.uniqueRequestId()); addRequest.setReturnData(returnData); String entityName = pso.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); if (entityName != null) { addRequest.addOpenContentAttr(Pso.ENTITY_NAME_ATTRIBUTE, entityName); } // identifier addRequest.setPsoID(pso.getPsoID()); addRequest.setTargetId(pso.getPsoID().getTargetID()); if (pso.getPsoID().getContainerID() != null) { addRequest.setContainerID(pso.getPsoID().getContainerID()); } // data if (returnData.equals(ReturnData.DATA) || returnData.equals(ReturnData.EVERYTHING)) { addRequest.setData(pso.getData()); } // everything if (returnData.equals(ReturnData.EVERYTHING)) { for (CapabilityData capabilityData : pso.getCapabilityData()) { addRequest.addCapabilityData(capabilityData); } } return addRequest; } /** * Diff the current and correct provisioning service objects with the same identifier and return the modification * requests necessary to transform the current object to be identical to the correct object. * * @param correctPso the representation of the object as it should be * @param currentPso the representation of the object as it currently is * @param returnData whether to compare identifiers and/or data and/or references * @return the modify requests necessary for synchronization * @throws PspException if the objects do not have the same identifier or schema entity name * @throws Spml2Exception if an spml error occurs */ public List<ModifyRequest> diff(PSO correctPso, PSO currentPso, ReturnData returnData) throws PspException, Spml2Exception { List<ModifyRequest> modifyRequests = new ArrayList<ModifyRequest>(); // entityName String correctEntityName = correctPso.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); String currentEntityName = currentPso.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); if (!correctEntityName.equals(currentEntityName)) { LOG.error("Unable to diff objects with different entityNames : '{}' and '{}'", correctEntityName, currentEntityName); throw new PspException("Unable to diff objects with different entityNames."); } List<Modification> dataMods = Collections.EMPTY_LIST; List<Modification> referenceMods = Collections.EMPTY_LIST; if (returnData.equals(ReturnData.DATA) || returnData.equals(ReturnData.EVERYTHING)) { dataMods = diffData(correctPso, currentPso); } if (returnData.equals(ReturnData.EVERYTHING)) { referenceMods = diffReferences(correctPso, currentPso); } if (dataMods.isEmpty() && referenceMods.isEmpty()) { return modifyRequests; } if (getTarget(correctPso.getPsoID().getTargetID()).isBundleModifications()) { ModifyRequest modifyRequest = new ModifyRequest(); modifyRequest.setRequestID(PSPUtil.uniqueRequestId()); modifyRequest.setPsoID(correctPso.getPsoID()); if (correctEntityName != null) { modifyRequest.addOpenContentAttr(Pso.ENTITY_NAME_ATTRIBUTE, correctEntityName); } for (Modification modification : dataMods) { modifyRequest.addModification(modification); } for (Modification modification : referenceMods) { modifyRequest.addModification(modification); } modifyRequests.add(modifyRequest); } else { modifyRequests.addAll(unbundleDataModifications(dataMods, correctPso.getPsoID(), correctEntityName)); modifyRequests.addAll( unbundleReferenceModifications(referenceMods, correctPso.getPsoID(), correctEntityName)); } return modifyRequests; } /** * Diff the data of two Provisioning Service Objects. @see #diff(PSO, PSO) * * @param correctPSO the representation of the PSO as it should be * @param currentPSO the representation of the PSO as it is * @return the <code>ModifyRequests</code> which would make the currentPSO identical to the correctPSO * @throws DSMLProfileException if an error occurs determining the <code>ModifyRequest</code>s * @throws PspException if the Provisioning Service Objects do not have an * <code>PSODefinition.ENTITY_NAME_ATTRIBUTE</code> */ public List<Modification> diffData(PSO correctPSO, PSO currentPSO) throws DSMLProfileException, PspException { List<Modification> modifications = new ArrayList<Modification>(); Map<String, DSMLAttr> currentDsmlAttrs = PSPUtil.getDSMLAttrMap(currentPSO.getData()); Map<String, DSMLAttr> correctDsmlAttrs = PSPUtil.getDSMLAttrMap(correctPSO.getData()); Set<String> attrNames = new LinkedHashSet<String>(); attrNames.addAll(correctDsmlAttrs.keySet()); attrNames.addAll(currentDsmlAttrs.keySet()); // determine the schema entity, assume pso IDs are the same for each pso, where do we // check this ? String targetId = currentPSO.getPsoID().getTargetID(); String entityName = currentPSO.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); Pso psoDefinition = getPso(targetId, entityName); if (psoDefinition == null) { LOG.error("Unknown pso for target id '" + targetId + "' entity name '" + entityName + "'"); throw new PspException("Unknown pso for target id '" + targetId + "' entity name '" + entityName + "'"); } for (String attrName : attrNames) { PsoAttribute psoAttributeDefinition = psoDefinition.getPsoAttribute(attrName); if (psoAttributeDefinition == null) { LOG.error("Unknown pso attribute '" + attrName + "'"); throw new PspException("Unknown pso attribute '" + attrName + "'"); } } for (String attrName : attrNames) { DSMLAttr currentDsmlAttr = currentDsmlAttrs.get(attrName); DSMLAttr correctDsmlAttr = correctDsmlAttrs.get(attrName); AttributeModifier attributeModifier = new AttributeModifier(attrName, true); if (currentDsmlAttr != null) { attributeModifier.initDSML(currentDsmlAttr.getValues()); PsoAttribute psoAttributeDefinition = psoDefinition.getPsoAttribute(attrName); if (psoAttributeDefinition.isRetainAll()) { attributeModifier.retainAll(); } if (psoAttributeDefinition.isReplaceValues()) { attributeModifier.setReplaceValues(true); } } if (correctDsmlAttr != null) { attributeModifier.store(correctDsmlAttr.getValues()); } modifications.addAll(attributeModifier.getDSMLModification()); } return modifications; } /** * Diff the reference capability data of two Provisioning Service Objects. @see #diff(PSO, PSO) * * @param correctPSO the representation of the PSO as it should be * @param currentPSO the representation of the PSO as it is * @return the <code>ModifyRequests</code> which would make the currentPSO identical to the correctPSO * @throws Spml2Exception if an error occurs determining the <code>ModifyRequest</code>s * @throws PspException if the reference capability can not be handled */ public List<Modification> diffReferences(PSO correctPSO, PSO currentPSO) throws Spml2Exception, PspException { List<Modification> modifications = new ArrayList<Modification>(); Map<String, List<Reference>> correctReferenceMap = PSPUtil.getReferences(correctPSO.getCapabilityData()); Map<String, List<Reference>> currentReferenceMap = PSPUtil.getReferences(currentPSO.getCapabilityData()); Set<String> typeOfReferences = new LinkedHashSet<String>(); typeOfReferences.addAll(correctReferenceMap.keySet()); typeOfReferences.addAll(currentReferenceMap.keySet()); // determine the schema entity String targetId = correctPSO.getPsoID().getTargetID(); String entityName = correctPSO.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); Pso psoDefinition = getPso(targetId, entityName); if (psoDefinition == null) { LOG.error("Unknown pso for target id '" + targetId + "' entity name '" + entityName + "'"); throw new PspException("Unknown pso for target id '" + targetId + "' entity name '" + entityName + "'"); } for (String typeOfReference : typeOfReferences) { List<Reference> currentReferences = currentReferenceMap.get(typeOfReference); List<Reference> correctReferences = correctReferenceMap.get(typeOfReference); PsoReferences psoReferences = psoDefinition.getReferences(typeOfReference); if (psoReferences == null) { LOG.error("Unknown pso references type '" + typeOfReference + "'"); throw new PspException("Unknown pso references type '" + typeOfReference + "'"); } AttributeModifier attributeModifier = new AttributeModifier(typeOfReference, psoReferences.isCaseSensitive()); if (currentReferences != null) { attributeModifier.initReference(currentReferences); } if (correctReferences != null) { attributeModifier.store(correctReferences); } modifications.addAll(attributeModifier.getReferenceModification()); } return modifications; } /** * Lookup an identifier and return true if the lookup is successful and the identifier exists. Return false if the * lookup is successful and the identifier does not exist. Throw exception if the attempt to lookup the identifier * does not succeed. * * @param psoID the provisioned object identifier * @return true if the identifier exists, false if the identifier does not exist * @throws PspException if an error occurs during when attempting to lookup the identifier */ public boolean doesIdentifierExist(PSOIdentifier psoID) throws PspException { LookupRequest lookupRequest = new LookupRequest(); lookupRequest.setPsoID(psoID); lookupRequest.setRequestID(PSPUtil.uniqueRequestId()); lookupRequest.setReturnData(ReturnData.IDENTIFIER); LookupResponse lookupResponse = execute(lookupRequest); return doesIdentifierExist(lookupResponse); } /** * Returns true if the lookup response is successful and the identifier exists. Returns false if the lookup response * is successful and the identifier does not exist. Throw exception otherwise. * * @param lookupResponse the lookup response * @return true if the identifier exists, false if the identifier does not exist * @throws PspException if the lookup response is not successful */ public static boolean doesIdentifierExist(LookupResponse lookupResponse) throws PspException { if (lookupResponse.getStatus().equals(StatusCode.SUCCESS)) { return true; } if (lookupResponse.getStatus().equals(StatusCode.FAILURE) && lookupResponse.getError().equals(ErrorCode.NO_SUCH_IDENTIFIER)) { return false; } LOG.error("The lookup response is not a success '{}'", PSPUtil.toString(lookupResponse)); throw new PspException("The lookup response is not a success '" + PSPUtil.toString(lookupResponse) + "'"); } /** {@inheritDoc} */ public void execute(AddRequest addRequest, AddResponse addResponse) { // Get the target definition. SpmlTarget target = targets.get(addRequest.getPsoID().getTargetID()); // Execute the request on the target provider. Response targetResponse = target.execute(addRequest); // Fail if the response is not the correct class. if (!(targetResponse instanceof AddResponse)) { fail(addResponse, ErrorCode.CUSTOM_ERROR, "Target did not return an AddResponse."); return; } // If successful, set the PSO. if (targetResponse.getStatus().equals(StatusCode.SUCCESS)) { addResponse.setPso(((AddResponse) targetResponse).getPso()); } else { // If not successful, fail the response. fail(addResponse, targetResponse.getError(), targetResponse.getErrorMessages()); } } /** * Execute a calc request for every source identifier. * * @param bulkCalcRequest the request * @return the response containing a calc response for every source identifier */ public BulkCalcResponse execute(BulkCalcRequest bulkCalcRequest) { // Start MDC logging. MDCHelper mdc = new MDCHelper(bulkCalcRequest).start(); // Log the request. LOG.info("Psp '{}' - BulkCalc {}", getId(), PSPUtil.toString(bulkCalcRequest)); // Log the request as SPML. if (isLogSpml()) { LOG.info("Psp '{}' - BulkCalc SPML:\n{}", getId(), toXML(bulkCalcRequest)); } // Potentially write the request. writeRequest(bulkCalcRequest); // Create a new response. BulkCalcResponse bulkCalcResponse = new BulkCalcResponse(); // Be optimistic regarding success. bulkCalcResponse.setStatus(StatusCode.SUCCESS); // The response requestID should be the same as the request. bulkCalcResponse.setRequestID(getOrGenerateRequestID(bulkCalcRequest)); // Validate the request. validate(bulkCalcRequest, bulkCalcResponse); // If the validation was successful, execute the request. if (bulkCalcResponse.getStatus().equals(StatusCode.SUCCESS)) { execute(bulkCalcRequest, bulkCalcResponse); } // If the response is a success, log to INFO. if (bulkCalcResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - BulkCalc {}", getId(), PSPUtil.toString(bulkCalcResponse)); if (isLogSpml()) { LOG.info("Psp '{}' - BulkCalc SPML:\n{}", getId(), toXML(bulkCalcResponse)); } // If the response is not a success, log to ERROR. } else { LOG.error("Psp '{}' - BulkCalc {}", getId(), PSPUtil.toString(bulkCalcResponse)); if (isLogSpml()) { LOG.error("Psp '{}' - BulkCalc SPML:\n{}", getId(), toXML(bulkCalcResponse)); } } // Potentially write the response. writeResponse(bulkCalcResponse); // Stop MDC logging. mdc.stop(); // Return the response. return bulkCalcResponse; } /** * Execute an {@link BulkCalcRequest} and update the {@link BulkCalcResponse}. * * @param bulkCalcRequest the SPML bulk calc request * @param bulkCalcResponse the SPML bulk calc response */ public void execute(BulkCalcRequest bulkCalcRequest, BulkCalcResponse bulkCalcResponse) { // get all identifiers Map<String, List<SchemaEntityRef>> identifiers = null; try { identifiers = getAllSourceIdentifiers(bulkCalcRequest); } catch (AttributeRequestException e) { fail(bulkCalcResponse, ErrorCode.CUSTOM_ERROR, e); return; } catch (PspException e) { fail(bulkCalcResponse, ErrorCode.CUSTOM_ERROR, e); return; } if (identifiers == null) { fail(bulkCalcResponse, ErrorCode.CUSTOM_ERROR, "Unable to resolve source identifiers."); return; } // by creating the psp context here, references will be cached PspContext pspContext = new PspContext(); pspContext.setCalcRequestMap(new HashMap<CalcRequest, CalcResponse>(identifiers.size())); // new CalcRequest for each identifier for (String identifier : identifiers.keySet()) { CalcRequest calcRequest = new CalcRequest(); calcRequest.setId(identifier); calcRequest.setRequestID(PSPUtil.uniqueRequestId()); calcRequest.setReturnData(bulkCalcRequest.getReturnData()); calcRequest.setSchemaEntities(identifiers.get(identifier)); CalcResponse calcResponse = execute(calcRequest, pspContext); bulkCalcResponse.addResponse(calcResponse); // first failure encountered, stop processing if OnError.EXIT if (calcResponse.getStatus() != StatusCode.SUCCESS && bulkCalcResponse.getStatus() != StatusCode.FAILURE) { bulkCalcResponse.setStatus(StatusCode.FAILURE); if (bulkCalcRequest.getOnError().equals(OnError.EXIT)) { return; } } } } /** * Execute a diff request for every source identifier. * * @param bulkDiffRequest the request * @return the response containing a diff response for every source identifier */ public BulkDiffResponse execute(BulkDiffRequest bulkDiffRequest) { // Start MDC logging. MDCHelper mdc = new MDCHelper(bulkDiffRequest).start(); // Log the request. LOG.info("Psp '{}' - BulkDiff {}", getId(), PSPUtil.toString(bulkDiffRequest)); // Log the request as SPML. if (isLogSpml()) { LOG.info("Psp '{}' - BulkDiff SPML:\n{}", getId(), toXML(bulkDiffRequest)); } // Potentially write the request. writeRequest(bulkDiffRequest); // Create a new response. BulkDiffResponse bulkDiffResponse = new BulkDiffResponse(); // Be optimistic regarding success. bulkDiffResponse.setStatus(StatusCode.SUCCESS); // The response requestID should be the same as the request. bulkDiffResponse.setRequestID(getOrGenerateRequestID(bulkDiffRequest)); // Validate the request. validate(bulkDiffRequest, bulkDiffResponse); // If the validation was successful, execute the request. if (bulkDiffResponse.getStatus().equals(StatusCode.SUCCESS)) { execute(bulkDiffRequest, bulkDiffResponse); } // If the response is a success, log to INFO. if (bulkDiffResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - BulkDiff {}", getId(), PSPUtil.toString(bulkDiffResponse)); if (isLogSpml()) { LOG.info("Psp '{}' - BulkDiff SPML:\n{}", getId(), toXML(bulkDiffResponse)); } // If the response is not a success, log to ERROR. } else { LOG.error("Psp '{}' - BulkDiff {}", getId(), PSPUtil.toString(bulkDiffResponse)); if (isLogSpml()) { LOG.error("Psp '{}' - BulkDiff SPML:\n{}", getId(), toXML(bulkDiffResponse)); } } // Potentially write the response. writeResponse(bulkDiffResponse); // Stop MDC logging. mdc.stop(); // Return the response. return bulkDiffResponse; } /** * Execute an {@link BulkDiffRequest} and update the {@link BulkDiffResponse}. * * @param bulkDiffRequest the SPML bulk diff request * @param bulkDiffResponse the SPML bulk diff response */ public void execute(BulkDiffRequest bulkDiffRequest, BulkDiffResponse bulkDiffResponse) { try { // get all source identifiers Map<String, List<SchemaEntityRef>> identifiers = null; try { identifiers = getAllSourceIdentifiers(bulkDiffRequest); } catch (AttributeRequestException e) { fail(bulkDiffResponse, ErrorCode.CUSTOM_ERROR, e); return; } catch (PspException e) { fail(bulkDiffResponse, ErrorCode.CUSTOM_ERROR, e); return; } if (identifiers == null) { fail(bulkDiffResponse, ErrorCode.CUSTOM_ERROR, "Unable to resolve source identifiers."); return; } // get target identifiers which currently exist Set<PSOIdentifier> currentPsoIds = getAllTargetIdentifiers(bulkDiffRequest, bulkDiffResponse); if (currentPsoIds == null) { return; } // PSOIdentifiers that should exist Set<PSOIdentifier> correctPsoIds = new LinkedHashSet<PSOIdentifier>(); // PSOIdentifiers to be deleted Set<PSOIdentifier> psoIdsToBeDeleted = new LinkedHashSet<PSOIdentifier>(); // by creating the psp context here, references will be cached PspContext pspContext = new PspContext(); pspContext.setCalcRequestMap(new HashMap<CalcRequest, CalcResponse>(identifiers.size())); // diff each identifier for (String identifier : identifiers.keySet()) { // new diff request DiffRequest diffRequest = new DiffRequest(); diffRequest.setId(identifier); diffRequest.setRequestID(PSPUtil.uniqueRequestId()); diffRequest.setReturnData(bulkDiffRequest.getReturnData()); diffRequest.setSchemaEntities(identifiers.get(identifier)); // execute diff request DiffResponse diffResponse = execute(diffRequest, pspContext); // add diff response to bulk response ? boolean addToBulkResponse = false; if (bulkDiffRequest.returnDiffResponses()) { if (!diffResponse.getRequests().isEmpty()) { addToBulkResponse = true; } } if (bulkDiffRequest.returnSyncResponses()) { if (!diffResponse.getSynchronizedResponses().isEmpty()) { addToBulkResponse = true; } } if (addToBulkResponse) { bulkDiffResponse.addResponse(diffResponse); } // store correct ids and ids to be deleted for reconciliation for (AddRequest addRequest : diffResponse.getAddRequests()) { correctPsoIds.add(addRequest.getPsoID()); } for (ModifyRequest modifyRequest : diffResponse.getModifyRequests()) { correctPsoIds.add(modifyRequest.getPsoID()); } for (DeleteRequest deleteRequest : diffResponse.getDeleteRequests()) { psoIdsToBeDeleted.add(deleteRequest.getPsoID()); } for (SynchronizedResponse synchronizedResponse : diffResponse.getSynchronizedResponses()) { correctPsoIds.add(synchronizedResponse.getPsoID()); } // first failure encountered, stop processing if OnError.EXIT if (diffResponse.getStatus() != StatusCode.SUCCESS && bulkDiffResponse.getStatus() != StatusCode.FAILURE) { bulkDiffResponse.setStatus(StatusCode.FAILURE); if (bulkDiffRequest.getOnError().equals(OnError.EXIT)) { return; } } } // DeleteRequests for identifiers which exist but shouldn't, and which are not already being deleted for (PSOIdentifier psoId : currentPsoIds) { if (!correctPsoIds.contains(psoId) && !psoIdsToBeDeleted.contains(psoId)) { if (bulkDiffRequest.returnDiffResponses()) { DeleteRequest deleteRequest = new DeleteRequest(); deleteRequest.setPsoID(psoId); deleteRequest.setRequestID(PSPUtil.uniqueRequestId()); DiffResponse diffResponse = new DiffResponse(); diffResponse.setId(psoId.getID()); diffResponse.addRequest(deleteRequest); bulkDiffResponse.addResponse(diffResponse); } } } } catch (PspException e) { fail(bulkDiffResponse, ErrorCode.CUSTOM_ERROR, e); } catch (Spml2Exception e) { fail(bulkDiffResponse, ErrorCode.CUSTOM_ERROR, e); } } /** * Execute a sync request for every source identifier. * * @param bulkSyncRequest the request * @return the response containing a sync response for every source identifier */ public BulkSyncResponse execute(BulkSyncRequest bulkSyncRequest) { // Start MDC logging. MDCHelper mdc = new MDCHelper(bulkSyncRequest).start(); // Log the request. LOG.info("Psp '{}' - BulkSync {}", getId(), PSPUtil.toString(bulkSyncRequest)); // Log the request as SPML. if (isLogSpml()) { LOG.info("Psp '{}' - BulkSync SPML:\n{}", getId(), toXML(bulkSyncRequest)); } // Potentially write the request. writeRequest(bulkSyncRequest); // Create a new response. BulkSyncResponse bulkSyncResponse = new BulkSyncResponse(); // Be optimistic regarding success. bulkSyncResponse.setStatus(StatusCode.SUCCESS); // The response requestID should be the same as the request. bulkSyncResponse.setRequestID(getOrGenerateRequestID(bulkSyncRequest)); // Validate the request. validate(bulkSyncRequest, bulkSyncResponse); // If the validation was successful, execute the request. if (bulkSyncResponse.getStatus().equals(StatusCode.SUCCESS)) { execute(bulkSyncRequest, bulkSyncResponse); } // If the response is a success, log to INFO. if (bulkSyncResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - BulkSync {}", getId(), PSPUtil.toString(bulkSyncResponse)); if (isLogSpml()) { LOG.info("Psp '{}' - BulkSync SPML:\n{}", getId(), toXML(bulkSyncResponse)); } // If the response is not a success, log to ERROR. } else { LOG.error("Psp '{}' - BulkSync {}", getId(), PSPUtil.toString(bulkSyncResponse)); if (isLogSpml()) { LOG.error("Psp '{}' - BulkSync SPML:\n{}", getId(), toXML(bulkSyncResponse)); } } // Potentially write the response. writeResponse(bulkSyncResponse); // Stop MDC logging. mdc.stop(); // Return the response. return bulkSyncResponse; } /** * Execute an {@link BulkSyncRequest} and update the {@link BulkSyncResponse}. * * @param bulkSyncRequest the SPML bulk sync request * @param bulkSyncResponse the SPML bulk sync response */ public void execute(BulkSyncRequest bulkSyncRequest, BulkSyncResponse bulkSyncResponse) { try { // get all source identifiers Map<String, List<SchemaEntityRef>> identifiers = null; try { identifiers = getAllSourceIdentifiers(bulkSyncRequest); } catch (AttributeRequestException e) { fail(bulkSyncResponse, ErrorCode.CUSTOM_ERROR, e); return; } catch (PspException e) { fail(bulkSyncResponse, ErrorCode.CUSTOM_ERROR, e); return; } if (identifiers == null) { fail(bulkSyncResponse, ErrorCode.CUSTOM_ERROR, "Unable to resolve source identifiers."); return; } // get target identifiers which currently exist Set<PSOIdentifier> currentPsoIds = getAllTargetIdentifiers(bulkSyncRequest, bulkSyncResponse); if (currentPsoIds == null) { return; } // PSOIdentifiers that should exist Set<PSOIdentifier> correctPsoIds = new LinkedHashSet<PSOIdentifier>(); // PSOIdentifiers to be deleted Set<PSOIdentifier> psoIdsToBeDeleted = new LinkedHashSet<PSOIdentifier>(); // by creating the psp context here, references will be cached PspContext pspContext = new PspContext(); pspContext.setCalcRequestMap(new HashMap<CalcRequest, CalcResponse>(identifiers.size())); // sync each identifier for (String identifier : identifiers.keySet()) { // new sync request SyncRequest syncRequest = new SyncRequest(); syncRequest.setId(identifier); syncRequest.setRequestID(PSPUtil.uniqueRequestId()); syncRequest.setReturnData(bulkSyncRequest.getReturnData()); syncRequest.setSchemaEntities(identifiers.get(identifier)); // execute sync request SyncResponse syncResponse = execute(syncRequest, pspContext); // add sync response to bulk response ? boolean addToBulkResponse = false; if (bulkSyncRequest.returnDiffResponses()) { if (!syncResponse.getAddDeleteModifyResponses().isEmpty()) { addToBulkResponse = true; } } if (bulkSyncRequest.returnSyncResponses()) { if (!syncResponse.getSynchronizedResponses().isEmpty()) { addToBulkResponse = true; } } if (addToBulkResponse) { bulkSyncResponse.addResponse(syncResponse); } // store correct ids and ids to be deleted for reconciliation DiffResponse diffResponse = syncResponse.getDiffResponse(); for (AddRequest addRequest : diffResponse.getAddRequests()) { correctPsoIds.add(addRequest.getPsoID()); } for (ModifyRequest modifyRequest : diffResponse.getModifyRequests()) { correctPsoIds.add(modifyRequest.getPsoID()); } for (DeleteRequest deleteRequest : diffResponse.getDeleteRequests()) { psoIdsToBeDeleted.add(deleteRequest.getPsoID()); } for (SynchronizedResponse synchronizedResponse : diffResponse.getSynchronizedResponses()) { correctPsoIds.add(synchronizedResponse.getPsoID()); } // first failure encountered, stop processing if OnError.EXIT if (syncResponse.getStatus() != StatusCode.SUCCESS && bulkSyncResponse.getStatus() != StatusCode.FAILURE) { bulkSyncResponse.setStatus(StatusCode.FAILURE); if (bulkSyncRequest.getOnError().equals(OnError.EXIT)) { return; } } } // DeleteRequests for identifiers which exist but shouldn't, and which are not already being deleted for (PSOIdentifier psoId : currentPsoIds) { if (!correctPsoIds.contains(psoId) && !psoIdsToBeDeleted.contains(psoId)) { DeleteRequest deleteRequest = new DeleteRequest(); deleteRequest.setPsoID(psoId); deleteRequest.setRequestID(PSPUtil.uniqueRequestId()); DeleteResponse deleteResponse = execute(deleteRequest); SyncResponse syncResponse = new SyncResponse(); syncResponse.setId(psoId.getID()); syncResponse.addResponse(deleteResponse); syncResponse.setRequestID(deleteResponse.getRequestID()); syncResponse.setStatus(deleteResponse.getStatus()); if (deleteResponse.getStatus().equals(StatusCode.FAILURE)) { fail(syncResponse, deleteResponse.getError(), deleteResponse.getErrorMessages()); } if (bulkSyncRequest.returnDiffResponses()) { bulkSyncResponse.addResponse(syncResponse); } // first failure encountered, stop processing if OnError.EXIT if (syncResponse.getStatus() != StatusCode.SUCCESS && bulkSyncResponse.getStatus() != StatusCode.FAILURE) { bulkSyncResponse.setStatus(StatusCode.FAILURE); if (bulkSyncRequest.getOnError().equals(OnError.EXIT)) { return; } } } } } catch (PspException e) { fail(bulkSyncResponse, ErrorCode.CUSTOM_ERROR, e); } catch (Spml2Exception e) { fail(bulkSyncResponse, ErrorCode.CUSTOM_ERROR, e); } } /** {@inheritDoc} */ public CalcResponse execute(CalcRequest calcRequest) { return execute(calcRequest, new PspContext()); } /** * {@inheritDoc} * * The psp context argument allows for the caching of references during bulk requests. * * @param pspContext the psp context */ public CalcResponse execute(CalcRequest calcRequest, PspContext pspContext) { // Start MDC logging. MDCHelper mdc = new MDCHelper(calcRequest).start(); // Log the request. LOG.info("Psp '{}' - Calc {}", getId(), PSPUtil.toString(calcRequest)); // Log the request as SPML. if (isLogSpml()) { LOG.info("Psp '{}' - Calc XML:\n{}", getId(), toXML(calcRequest)); } // Potentially write the request. writeRequest(calcRequest); // Create a new response. CalcResponse calcResponse = new CalcResponse(); // Be optimistic regarding success. calcResponse.setStatus(StatusCode.SUCCESS); // The response requestID should be the same as the request. calcResponse.setRequestID(getOrGenerateRequestID(calcRequest)); // Validate the request. validate(calcRequest, calcResponse); // If the validation was successful, execute the request. if (calcResponse.getStatus().equals(StatusCode.SUCCESS)) { execute(calcRequest, calcResponse, pspContext); } // If the response is a success, log to INFO. if (calcResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - Calc {}", getId(), PSPUtil.toString(calcResponse)); if (isLogSpml()) { LOG.info("Psp '{}' - Calc XML:\n{}", getId(), toXML(calcResponse)); } // If the response is not a success, log to ERROR. } else { String errorMsg = PSPUtil.toString(calcResponse); // Special case the Unable to calculate provisioned object error. // this typically happens because the action to the object happens // outside of an area the PSP has knowledge of. GRP-811 if (errorMsg.contains("Unable to calculate provisioned object.")) { LOG.info("Psp '{}' - Calc {}", getId(), errorMsg); if (isLogSpml()) { LOG.info("Psp '{}' - Calc XML:\n{}", getId(), toXML(calcResponse)); } } else { LOG.error("Psp '{}' - Calc {}", getId(), errorMsg); if (isLogSpml()) { LOG.error("Psp '{}' - Calc XML:\n{}", getId(), toXML(calcResponse)); } } } // Potentially write the response. writeResponse(calcResponse); // Stop MDC logging. mdc.stop(); // Return the response. return calcResponse; } /** * Execute an {@link CalcRequest} and update the {@link CalcResponse}. * * The psp context argument allows for the caching of references during bulk requests. * * @param calcRequest the SPML calc request * @param calcResponse the SPML calc response * @param pspContext the psp context */ public void execute(CalcRequest calcRequest, CalcResponse calcResponse, PspContext pspContext) { try { // Set the response id. calcResponse.setId(calcRequest.getId()); // provisioning context // PspContext pspContext = new PspContext(); pspContext.setProvisioningServiceProvider(this); pspContext.setProvisioningRequest(calcRequest); pspContext.setAttributes(null); // attribute request context BaseSAMLProfileRequestContext attributeRequestContext = new BaseSAMLProfileRequestContext(); attributeRequestContext.setPrincipalName(calcRequest.getId()); // get targets specified in request before building the context Map<String, List<Pso>> map = getTargetAndObjectDefinitions(calcRequest); // determine attribute resolver requested attributes LinkedHashSet<String> attributeIds = new LinkedHashSet<String>(); for (String targetId : map.keySet()) { for (Pso psoDefinition : map.get(targetId)) { attributeIds.addAll(psoDefinition.getSourceIds(calcRequest.getReturnData())); } } attributeRequestContext.setRequestedAttributes(attributeIds); // resolve attributes LOG.debug("PSP '{}' - Calc {} Resolving attributes '{}'.", new Object[] { getId(), calcRequest, attributeIds }); Map<String, BaseAttribute<?>> attributes = getAttributeAuthority() .getAttributes(attributeRequestContext); LOG.debug("PSP '{}' - Calc {} Resolved attributes '{}'.", new Object[] { getId(), calcRequest, attributes.keySet() }); pspContext.setAttributes(attributes); // get PSOs based on attributes in psp context for (String targetId : map.keySet()) { for (Pso psoDefinition : map.get(targetId)) { for (PSO pso : psoDefinition.getPSO(pspContext)) { calcResponse.addPSO(pso); } } } if (calcResponse.getPSOs().isEmpty()) { fail(calcResponse, ErrorCode.NO_SUCH_IDENTIFIER, "Unable to calculate provisioned object."); return; } } catch (PspException e) { fail(calcResponse, ErrorCode.CUSTOM_ERROR, e); } catch (AttributeRequestException e) { fail(calcResponse, ErrorCode.CUSTOM_ERROR, e); } catch (Spml2Exception e) { fail(calcResponse, ErrorCode.CUSTOM_ERROR, e); } } /** {@inheritDoc} */ public void execute(DeleteRequest deleteRequest, DeleteResponse deleteResponse) { // Get the target definition. SpmlTarget target = targets.get(deleteRequest.getPsoID().getTargetID()); // Execute the request on the target provider. Response targetResponse = target.execute(deleteRequest); // Fail if the response is not the correct class. if (!(targetResponse instanceof DeleteResponse)) { fail(deleteResponse, ErrorCode.CUSTOM_ERROR, "Target did not return a DeleteResponse."); return; } if (targetResponse.getStatus().equals(StatusCode.SUCCESS)) { // Do nothing. } else { // If not successful, fail the response. fail(deleteResponse, targetResponse.getError(), targetResponse.getErrorMessages()); } } /** {@inheritDoc} */ public DiffResponse execute(DiffRequest diffRequest) { return execute(diffRequest, new PspContext()); } /** * {@inheritDoc} * * The psp context argument allows for the caching of references during bulk requests. * * @param pspContext the psp context */ public DiffResponse execute(DiffRequest diffRequest, PspContext pspContext) { // Start MDC logging. MDCHelper mdc = new MDCHelper(diffRequest).start(); // Log the request. LOG.info("Psp '{}' - Diff {}", getId(), PSPUtil.toString(diffRequest)); // Log the request as SPML. if (isLogSpml()) { LOG.info("Psp '{}' - Diff XML:\n{}", getId(), toXML(diffRequest)); } // Potentially write the request. writeRequest(diffRequest); // Create a new response. DiffResponse diffResponse = new DiffResponse(); // Be optimistic regarding success. diffResponse.setStatus(StatusCode.SUCCESS); // The response requestID should be the same as the request. diffResponse.setRequestID(getOrGenerateRequestID(diffRequest)); // Validate the request. validate(diffRequest, diffResponse); // If the validation was successful, execute the request. if (diffResponse.getStatus().equals(StatusCode.SUCCESS)) { execute(diffRequest, diffResponse, pspContext); } // If the response is a success, log to INFO. if (diffResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - Diff {}", getId(), PSPUtil.toString(diffResponse)); if (isLogSpml()) { LOG.info("Psp '{}' - Diff XML:\n{}", getId(), toXML(diffResponse)); } // If the response is not a success, log to ERROR. } else { LOG.error("Psp '{}' - Diff {}", getId(), PSPUtil.toString(diffResponse)); if (isLogSpml()) { LOG.error("Psp '{}' - Diff XML:\n{}", getId(), toXML(diffResponse)); } } // Potentially write the response. writeResponse(diffResponse); // Stop MDC logging. mdc.stop(); // Return the response. return diffResponse; } /** * Execute an {@link DiffRequest} and update the {@link DiffResponse}. * * The psp context argument allows for the caching of references during bulk requests. * * @param diffRequest the SPML diff request * @param diffResponse the SPML diff response * @param pspContext the psp context */ public void execute(DiffRequest diffRequest, DiffResponse diffResponse, PspContext pspContext) { diffResponse.setId(diffRequest.getId()); // Calculate how the id should be provisioned. CalcRequest calcRequest = new CalcRequest(); calcRequest.setId(diffRequest.getId()); calcRequest.setRequestID(PSPUtil.uniqueRequestId()); calcRequest.setReturnData(diffRequest.getReturnData()); calcRequest.setSchemaEntities(diffRequest.getSchemaEntities()); // Execute the calc request. CalcResponse calcResponse = execute(calcRequest, pspContext); if (calcResponse.getStatus().equals(StatusCode.FAILURE)) { fail(diffResponse, calcResponse.getError(), calcResponse.getErrorMessages()); return; } for (PSO correctPSO : calcResponse.getPSOs()) { // Lookup a PSO Identifier to see how it is provisioned. LookupRequest lookupRequest = new LookupRequest(); lookupRequest.setPsoID(correctPSO.getPsoID()); lookupRequest.setRequestID(PSPUtil.uniqueRequestId()); lookupRequest.setReturnData(diffRequest.getReturnData()); LookupResponse lookupResponse = execute(lookupRequest); try { if (Psp.doesIdentifierExist(lookupResponse)) { // if identifier exists, diff PSO currentPSO = lookupResponse.getPso(); List<ModifyRequest> modifyRequests = diff(correctPSO, currentPSO, diffRequest.getReturnData()); if (modifyRequests.isEmpty()) { SynchronizedResponse synchronizedResponse = new SynchronizedResponse(); synchronizedResponse.setPsoID(currentPSO.getPsoID()); diffResponse.addResponse(synchronizedResponse); } else { for (ModifyRequest modifyRequest : modifyRequests) { modifyRequest.setReturnData(diffRequest.getReturnData()); diffResponse.addRequest(modifyRequest); } } } else { // if identifier does not exist, do we need to rename ? ModifyRequest modifyRequest = renameRequest(correctPSO); // if modify request is not null, rename if (modifyRequest != null) { diffResponse.addRequest(modifyRequest); } else { // if not renaming, add AddRequest addRequest = createAddRequest(correctPSO, diffRequest.getReturnData()); diffResponse.addRequest(addRequest); } } } catch (Spml2Exception e) { fail(diffResponse, ErrorCode.CUSTOM_ERROR, e); } catch (PspException e) { fail(diffResponse, ErrorCode.CUSTOM_ERROR, e); } } } /** * Execute a list targets request and return the response. * * @param listTargetsRequest the list targets request * @return the list targets response */ public ListTargetsResponse execute(ListTargetsRequest listTargetsRequest) { MDCHelper mdc = new MDCHelper(listTargetsRequest).start(); LOG.info("Psp '{}' - ListTargets {}", getId(), listTargetsRequest); if (isLogSpml()) { LOG.info("Psp '{}' - ListTargets XML\n{}", getId(), toXML(listTargetsRequest)); } writeRequest(listTargetsRequest); ListTargetsResponse listTargetsResponse = new ListTargetsResponse(); listTargetsResponse.setStatus(StatusCode.SUCCESS); listTargetsResponse.setRequestID(getOrGenerateRequestID(listTargetsRequest)); try { for (String targetId : objects.keySet()) { listTargetsResponse.addTarget(getSpmlTarget(targetId)); } } catch (Spml2Exception e) { // FUTURE UNSUPPORTED_PROFILE instead of CUSTOM_ERROR as appropriate fail(listTargetsResponse, ErrorCode.CUSTOM_ERROR, e); } if (listTargetsResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - ListTargets {}", getId(), PSPUtil.toString(listTargetsResponse)); } else { LOG.error("Psp '{}' - ListTargets {}", getId(), PSPUtil.toString(listTargetsResponse)); } if (isLogSpml()) { LOG.info("Psp '{}' - ListTargets XML\n{}", getId(), toXML(listTargetsResponse)); } mdc.stop(); writeResponse(listTargetsResponse); return listTargetsResponse; } /** {@inheritDoc} */ public void execute(LookupRequest lookupRequest, LookupResponse lookupResponse) { // Get the target definition. SpmlTarget target = targets.get(lookupRequest.getPsoID().getTargetID()); // Execute the request on the target provider. Response targetResponse = target.execute(lookupRequest); // Fail if the response is not the correct class. if (!(targetResponse instanceof LookupResponse)) { fail(lookupResponse, ErrorCode.CUSTOM_ERROR, "Target did not return a LookupResponse."); return; } // If successful, set the PSO. if (targetResponse.getStatus().equals(StatusCode.SUCCESS)) { lookupResponse.setPso(((LookupResponse) targetResponse).getPso()); } else { // If not successful, fail the response. fail(lookupResponse, targetResponse.getError(), targetResponse.getErrorMessages()); } } /** {@inheritDoc} */ public void execute(ModifyRequest modifyRequest, ModifyResponse modifyResponse) { // Get the target definition. SpmlTarget target = targets.get(modifyRequest.getPsoID().getTargetID()); // Execute the request on the target provider. Response targetResponse = target.execute(modifyRequest); // Fail if the response is not the correct class. if (!(targetResponse instanceof ModifyResponse)) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, "Target did not return a ModifyResponse."); return; } // If successful, set the PSO. if (targetResponse.getStatus().equals(StatusCode.SUCCESS)) { modifyResponse.setPso(((ModifyResponse) targetResponse).getPso()); } else { // If not successful, fail the response. fail(modifyResponse, targetResponse.getError(), targetResponse.getErrorMessages()); } } /** {@inheritDoc} */ public void execute(SearchRequest searchRequest, SearchResponse searchResponse) { // Get the target definition. SpmlTarget target = targets.get(searchRequest.getQuery().getTargetID()); // Execute the request on the target provider. Response targetResponse = target.execute(searchRequest); // Fail if the response is not the correct class. if (!(targetResponse instanceof SearchResponse)) { fail(searchResponse, ErrorCode.CUSTOM_ERROR, "Target did not return a SearchResponse."); return; } // If successful, add the PSOs. if (targetResponse.getStatus().equals(StatusCode.SUCCESS)) { for (PSO pso : ((SearchResponse) targetResponse).getPSOs()) { searchResponse.addPSO(pso); } } else { // If not successful, fail the response. fail(searchResponse, targetResponse.getError(), targetResponse.getErrorMessages()); } } /** {@inheritDoc} */ public SyncResponse execute(SyncRequest syncRequest) { return execute(syncRequest, new PspContext()); } /** * {@inheritDoc} * * The psp context argument allows for the caching of references during bulk requests. * * @param pspContext the psp context */ public SyncResponse execute(SyncRequest syncRequest, PspContext pspContext) { // Start MDC logging. MDCHelper mdc = new MDCHelper(syncRequest).start(); // Log the request. LOG.info("Psp '{}' - Sync {}", getId(), PSPUtil.toString(syncRequest)); // Log the request as SPML. if (isLogSpml()) { LOG.info("Psp '{}' - Sync SPML:\n{}", getId(), toXML(syncRequest)); } // Potentially write the request. writeRequest(syncRequest); // Create a new response. SyncResponse syncResponse = new SyncResponse(); // Be optimistic regarding success. syncResponse.setStatus(StatusCode.SUCCESS); // The response requestID should be the same as the request. syncResponse.setRequestID(getOrGenerateRequestID(syncRequest)); // Validate the request. validate(syncRequest, syncResponse); // If the validation was successful, execute the request. if (syncResponse.getStatus().equals(StatusCode.SUCCESS)) { execute(syncRequest, syncResponse, pspContext); } // If the response is a success, log to INFO. if (syncResponse.getStatus().equals(StatusCode.SUCCESS)) { LOG.info("Psp '{}' - Sync {}", getId(), PSPUtil.toString(syncResponse)); if (isLogSpml()) { LOG.info("Psp '{}' - Sync SPML:\n{}", getId(), toXML(syncResponse)); } // If the response is not a success, log to ERROR. } else { LOG.error("Psp '{}' - Sync {}", getId(), PSPUtil.toString(syncResponse)); if (isLogSpml()) { LOG.error("Psp '{}' - Sync SPML:\n{}", getId(), toXML(syncResponse)); } } // Potentially write the response. writeResponse(syncResponse); // Stop MDC logging. mdc.stop(); // Return the response. return syncResponse; } /** * Execute an {@link SyncRequest} and update the {@link SyncResponse}. * * @param syncRequest the SPML sync request * @param syncResponse the SPML sync response */ public void execute(SyncRequest syncRequest, SyncResponse syncResponse, PspContext pspContext) { // Set the response id. syncResponse.setId(syncRequest.getId()); // Create a diff request. DiffRequest diffRequest = new DiffRequest(); diffRequest.setId(syncRequest.getId()); diffRequest.setRequestID(PSPUtil.uniqueRequestId()); diffRequest.setReturnData(syncRequest.getReturnData()); diffRequest.setSchemaEntities(syncRequest.getSchemaEntities()); // Execute the diff request. DiffResponse diffResponse = execute(diffRequest, pspContext); // Store the diff response. syncResponse.setDiffResponse(diffResponse); // Return if the diff response is not successful. if (!diffResponse.getStatus().equals(StatusCode.SUCCESS)) { fail(syncResponse, diffResponse.getError(), diffResponse.getErrorMessages()); return; } try { // Execute the requests in the diff response. for (Request request : diffResponse.getRequests()) { Response response = execute(request); syncResponse.addResponse(response); if (request instanceof DeleteRequest) { syncResponse.setId(((DeleteRequest) request).getPsoID().getID()); } if (response.getStatus().equals(StatusCode.FAILURE)) { fail(syncResponse, response.getError(), response.getErrorMessages()); return; } } for (SynchronizedResponse synchronizedResponse : diffResponse.getSynchronizedResponses()) { syncResponse.addResponse(synchronizedResponse); } } catch (PspException e) { fail(syncResponse, ErrorCode.CUSTOM_ERROR, e); return; } catch (Spml2Exception e) { fail(syncResponse, ErrorCode.CUSTOM_ERROR, e); return; } } /** * This method returns all source object identifiers. The map keys are the identifiers, and the map values are the * {@link SchemaEntityRef}s applicable for each identifier. * * The identifiers are returned by the attribute resolver via a {@link BulkCalcRequest} whose return data is * "identifier". * * @param bulkProvisioningRequest the bulk provisioning request * @return a possibly empty map consisting of all source identifiers and their corresponding provisioned objects * @throws PspException * @throws AttributeRequestException */ public Map<String, List<SchemaEntityRef>> getAllSourceIdentifiers( BulkProvisioningRequest bulkProvisioningRequest) throws PspException, AttributeRequestException { BulkCalcRequest bulkCalcRequest = new BulkCalcRequest(); bulkCalcRequest.setSchemaEntities(bulkProvisioningRequest.getSchemaEntities()); bulkCalcRequest.setReturnData(ReturnData.IDENTIFIER); bulkCalcRequest.setId(BulkProvisioningRequest.BULK_REQUEST_ID); // provisioning context PspContext pspContext = new PspContext(); pspContext.setProvisioningServiceProvider(this); pspContext.setProvisioningRequest(bulkCalcRequest); // attribute request context BaseSAMLProfileRequestContext attributeRequestContext = new BaseSAMLProfileRequestContext(); attributeRequestContext.setPrincipalName(bulkCalcRequest.getId()); // get targets specified in request before building the context Map<String, List<Pso>> map = getTargetAndObjectDefinitions(bulkCalcRequest); // determine attribute resolver requested attributes LinkedHashSet<String> attributeIds = new LinkedHashSet<String>(); for (String psoTargetDefinition : map.keySet()) { for (Pso psoDefinition : map.get(psoTargetDefinition)) { if (!DatatypeHelper.isEmpty(psoDefinition.getAllSourceIdentifiersRef())) { attributeIds.add(DatatypeHelper.safeTrim(psoDefinition.getAllSourceIdentifiersRef())); } } } attributeRequestContext.setRequestedAttributes(attributeIds); // return null if there are no attribute ids to resovle if (attributeIds.isEmpty()) { LOG.debug("PSP '{}' - No source identifier refs are configured."); return null; } // resolve attributes LOG.debug("PSP '{}' - Calc {} Resolving attributes '{}'.", new Object[] { getId(), bulkCalcRequest, attributeIds }); Map<String, BaseAttribute<?>> attributes = getAttributeAuthority().getAttributes(attributeRequestContext); LOG.debug("PSP '{}' - Calc {} Resolved attributes '{}'.", new Object[] { getId(), bulkCalcRequest, attributeIds }); pspContext.setAttributes(attributes); Map<String, List<SchemaEntityRef>> identifierMap = new LinkedHashMap<String, List<SchemaEntityRef>>(); for (String targetId : map.keySet()) { for (Pso psoDefinition : map.get(targetId)) { String allSourceIdentifiersRef = psoDefinition.getAllSourceIdentifiersRef(); if (DatatypeHelper.isEmpty(allSourceIdentifiersRef)) { continue; } BaseAttribute attribute = attributes.get(allSourceIdentifiersRef); if (attribute == null) { LOG.warn("PSP '{}' - Unable to resolve attribute '{}'", getId(), allSourceIdentifiersRef); continue; } for (Object value : attribute.getValues()) { if (value == null) { throw new PspException("TODO null value"); } String id = null; if (value instanceof PSOIdentifier) { id = ((PSOIdentifier) value).getID(); } else { id = value.toString(); } if (!identifierMap.containsKey(id)) { identifierMap.put(id, new ArrayList<SchemaEntityRef>()); } SchemaEntityRef entity = new SchemaEntityRef(); entity.setEntityName(psoDefinition.getId()); entity.setTargetID(targetId); identifierMap.get(id).add(entity); } } } return identifierMap; } /** * Search for all known identifiers of a target during a bulk diff request. The search request is created using the * filter returned from the identifying attributes. The identifiers are returned in an order suitable for deletion. * If the pso is not authoritative it is omitted. If a container id is included in the search request, it is omitted * from the returned identifiers. * * @param bulkProvisioningRequest the bulk request * @param provisioningResponse the bulk response * @return the target pso identifiers in order suitable for deletion * @throws PspException * @throws DSMLProfileException */ public Set<PSOIdentifier> getAllTargetIdentifiers(BulkProvisioningRequest bulkProvisioningRequest, ProvisioningResponse provisioningResponse) throws PspException, DSMLProfileException { // the pso ids which currently exist Set<PSOIdentifier> currentPsoIds = new LinkedHashSet<PSOIdentifier>(); // the targets and objects applicable to the request Map<String, List<Pso>> map = getTargetAndObjectDefinitions(bulkProvisioningRequest); // for every target id for (String targetId : map.keySet()) { // the pso ids which currently exist for each target Set<PSOIdentifier> targetPsoIds = new LinkedHashSet<PSOIdentifier>(); // get the target SpmlTarget target = targets.get(targetId); // for every pso for (Pso psoDefinition : map.get(targetId)) { // if authoritative, get the PSOIdentifiers from the target provider if (!psoDefinition.isAuthoritative()) { continue; } // if no identifying attribute nor query, skip the pso if (psoDefinition.getPsoIdentifyingAttribute() == null || psoDefinition.getAllTargetIdentifiersQuery() == null) { continue; } // create a search request from the identifying attribute SearchRequest searchRequest = new SearchRequest(); searchRequest.setRequestID(PSPUtil.uniqueRequestId()); searchRequest.setReturnData(ReturnData.IDENTIFIER); Query query = psoDefinition.getAllTargetIdentifiersQuery(); searchRequest.setQuery(query); // execute the search request Response searchResponse = target.execute(searchRequest); if (!(searchResponse instanceof SearchResponse)) { fail(provisioningResponse, ErrorCode.CUSTOM_ERROR, "Target did not return a SearchResponse."); return null; } if (!searchResponse.getStatus().equals(StatusCode.SUCCESS)) { // If not successful, fail the response. fail(provisioningResponse, searchResponse.getError(), searchResponse.getErrorMessages()); return null; } // gather the pso identifiers for (PSO pso : ((SearchResponse) searchResponse).getPSOs()) { // do not return the container id if it is specified if (psoDefinition.getPsoIdentifier().getContainerId() == null) { targetPsoIds.add(pso.getPsoID()); } else if (!psoDefinition.getPsoIdentifier().getContainerId().equals(pso.getPsoID().getID())) { targetPsoIds.add(pso.getPsoID()); } } } // order for deletion currentPsoIds.addAll(target.orderForDeletion(targetPsoIds)); } return currentPsoIds; } /** * Get the attribute authority used to calculate provisioned objects. * * @return the attribute authority */ public AttributeAuthority getAttributeAuthority() { return attributeAuthority; } /** {@inheritDoc} */ public String getId() { return id; } /** * Return the names of all target attributes, references, identifiers, etc. for the given target id and return data. * * If return data is identifier, the names of identifier definitions and alternate identifier definitions are * returned. * * If return data is data, the names of identifiers as well as attributes are returned. * * If return data is everything, the names of identifiers, attributes, and references are returned. * * @param targetId the target id * @param returnData return data * @return possibly empty set of attribute, reference, and identifier names */ public Set<String> getNames(String targetId, ReturnData returnData) { Set<String> names = new LinkedHashSet<String>(); List<Pso> psoDefinitions = objects.get(targetId); if (psoDefinitions != null) { for (Pso psoDefinition : psoDefinitions) { PsoIdentifyingAttribute ia = psoDefinition.getPsoIdentifyingAttribute(); if (ia != null) { names.add(ia.getName()); } if (returnData.equals(ReturnData.DATA) || returnData.equals(ReturnData.EVERYTHING)) { names.addAll(psoDefinition.getAttributeNames()); } if (returnData.equals(ReturnData.EVERYTHING)) { names.addAll(psoDefinition.getReferenceNames()); } } } return names; } /** * Return the pso definition with the given target id and object id (entity name) or null. * * @param targetId the target id * @param objectId the object id (entity name) * @return the pso definition or null */ public Pso getPso(String targetId, String objectId) { if (targetId == null || objectId == null) { return null; } if (!objects.containsKey(targetId)) { return null; } Pso psoDefinitionToReturn = null; for (Pso psoDefinition : objects.get(targetId)) { if (psoDefinition.getId().equals(objectId)) { psoDefinitionToReturn = psoDefinition; } } return psoDefinitionToReturn; } /** * Returns a list of pso definitions for the given target id. Returns null if the target id is null or the target id * is unknown. * * @param targetId the target id * @return list of pso definitions for the given target id or null */ public List<Pso> getPsos(String targetId) { if (targetId == null) { return null; } if (!objects.containsKey(targetId)) { return null; } return Collections.unmodifiableList(objects.get(targetId)); } /** * @return Returns the pspOptions. */ public PspOptions getPspOptions() { return pspOptions; } /** * Return the SPMLv2 target object for the given target id suitable for inclusion in a list targets request. * * @param targetId the target id * @return the SPMLv2 target object or null if the target id is unknown. * @throws Spml2Exception if an SPMLv2 toolkit error occursF */ public org.openspml.v2.msg.spml.Target getSpmlTarget(String targetId) throws Spml2Exception { if (!objects.containsKey(targetId)) { return null; } org.openspml.v2.msg.spml.Target target = new org.openspml.v2.msg.spml.Target(); target.setTargetID(targetId); // FUTURE support XSD ? target.setProfile(new DSMLProfileRegistrar().getProfileURI()); Schema schema = new Schema(); // FUTURE support other schemas ? DSMLSchema dsmlSchema = new DSMLSchema(); CapabilitiesList cl = new CapabilitiesList(); LinkedHashMap<String, SchemaEntityRef> schemaEntityRefMap = new LinkedHashMap<String, SchemaEntityRef>(); for (Pso psoDefinition : objects.get(targetId)) { SchemaEntityRef entity = new SchemaEntityRef(); entity.setEntityName(psoDefinition.getId()); entity.setTargetID(getId()); schemaEntityRefMap.put(entity.getEntityName(), entity); schema.addSupportedSchemaEntity(entity); ObjectClassDefinition objectClassDef = new ObjectClassDefinition(); objectClassDef.setName(psoDefinition.getId()); AttributeDefinitionReferences attrRefs = new AttributeDefinitionReferences(); for (PsoAttribute psoAttributeDefinition : psoDefinition.getPsoAttributes()) { AttributeDefinition attrDef = new AttributeDefinition(); attrDef.setName(psoAttributeDefinition.getName()); dsmlSchema.addAttributeDefinition(attrDef); AttributeDefinitionReference attrDefRef = new AttributeDefinitionReference(); attrDefRef.setName(psoAttributeDefinition.getName()); // FUTURE attrRef.setRequired(required); attrRefs.addAttributeDefinitionReference(attrDefRef); } objectClassDef.setMemberAttributes(attrRefs); dsmlSchema.addObjectClassDefinition(objectClassDef); } for (Pso psoDefinition : objects.get(targetId)) { for (PsoReferences psoReferencesDefinition : psoDefinition.getReferences()) { for (PsoReference psoReferenceDefinition : psoReferencesDefinition.getPsoReferences()) { SchemaEntityRef fromEntity = schemaEntityRefMap.get(psoDefinition.getId()); SchemaEntityRef toEntity = schemaEntityRefMap.get(psoReferenceDefinition.getToObject().getId()); Capability capability = new Capability(); capability.setNamespaceURI(PsoReferences.REFERENCE_URI); capability.addAppliesTo(fromEntity); cl.addCapability(capability); ReferenceDefinition rd = new ReferenceDefinition(); rd.setTypeOfReference(psoReferencesDefinition.getName()); rd.setSchemaEntity(fromEntity); rd.addCanReferTo(toEntity); OCEtoMarshallableAdapter oce = new OCEtoMarshallableAdapter(rd); capability.addOpenContentElement(oce); } } } target.setCapabilities(cl); schema.addOpenContentElement(dsmlSchema); target.addSchema(schema); return target; } /** * Return the {@link SpmlTarget} with the given target id or null. * * @param targetId the target id * @return the {@link SpmlTarget} or null. */ public SpmlTarget getTarget(String targetId) { return targets.get(targetId); } /** * Return a map whose keys are target IDs and whose values are lists of PSO definitions applicable to the schema * entity of the supplied provisioning request. * * @param request the provisioning request * @return map whose keys are target IDs and values are lists of PSO definitions * @throws PspException if the schema entity target id or entity name is unknown */ public Map<String, List<Pso>> getTargetAndObjectDefinitions(ProvisioningRequest request) throws PspException { Map<String, List<Pso>> map = new LinkedHashMap<String, List<Pso>>(); if (request.getSchemaEntities().isEmpty()) { map.putAll(getTargetAndObjectDefinitions(new SchemaEntityRef())); } else { for (SchemaEntityRef schemaEntityRef : request.getSchemaEntities()) { map.putAll(getTargetAndObjectDefinitions(schemaEntityRef)); } } return map; } /** * Return a map whose keys are target IDs and whose values are lists of PSO definitions applicable to the given * schema entity. * * @param schemaEntityRef the schema entity * @return map whose keys are target IDs and values are lists of PSO definitions * @throws PspException if the schema entity target id or entity name is unknown */ public Map<String, List<Pso>> getTargetAndObjectDefinitions(SchemaEntityRef schemaEntityRef) throws PspException { LOG.trace("PSP '{}' - Get pso definitions for schema entity '{}'", getId(), PSPUtil.toString(schemaEntityRef)); Map<String, List<Pso>> map = new LinkedHashMap<String, List<Pso>>(); if (DatatypeHelper.isEmpty(schemaEntityRef.getTargetID()) && DatatypeHelper.isEmpty(schemaEntityRef.getEntityName())) { map = Collections.unmodifiableMap(objects); } else if (DatatypeHelper.isEmpty(schemaEntityRef.getTargetID())) { for (String targetId : objects.keySet()) { for (Pso psoDefinition : objects.get(targetId)) { if (psoDefinition.getId().equals(schemaEntityRef.getEntityName())) { map.put(targetId, new ArrayList<Pso>()); map.get(targetId).add(psoDefinition); } } } if (map.isEmpty()) { LOG.error("Unknown pso id '" + schemaEntityRef.getEntityName() + "'"); throw new PspException("Unknown pso id '" + schemaEntityRef.getEntityName() + "'"); } } else if (DatatypeHelper.isEmpty(schemaEntityRef.getEntityName())) { if (!objects.containsKey(schemaEntityRef.getTargetID())) { LOG.error("Unknown target id '" + schemaEntityRef.getTargetID() + "'"); throw new PspException("Unknown target id '" + schemaEntityRef.getTargetID() + "'"); } map.put(schemaEntityRef.getTargetID(), objects.get(schemaEntityRef.getTargetID())); } else { if (!objects.containsKey(schemaEntityRef.getTargetID())) { LOG.error("Unknown target id '" + schemaEntityRef.getTargetID() + "'"); throw new PspException("Unknown target id '" + schemaEntityRef.getTargetID() + "'"); } for (Pso psoDefinition : objects.get(schemaEntityRef.getTargetID())) { if (psoDefinition.getId().equals(schemaEntityRef.getEntityName())) { map.put(schemaEntityRef.getTargetID(), new ArrayList<Pso>()); map.get(schemaEntityRef.getTargetID()).add(psoDefinition); } } if (map.isEmpty()) { LOG.error("Unknown pso id '" + schemaEntityRef.getEntityName() + "'"); throw new PspException("Unknown pso id '" + schemaEntityRef.getEntityName() + "'"); } } LOG.trace("PSP '{}' - Get pso definitions for schema entity '{}' found {}", new Object[] { getId(), PSPUtil.toString(schemaEntityRef), map }); return map; } /** * Return true if the given {@link PSOIdentifier} has an attribute with the given name and value. * * @param psoID the pso identifier * @param attributeName the attribute name * @param attributeValue the attribute value * @return true if the pso identifier has the attribute, false otherwise * @throws PspException if the spml search fails * @throws DSMLProfileException if a dsml error occurs * @throws PspNoSuchIdentifierException if the psoID can not be found */ public boolean hasAttribute(PSOIdentifier psoID, String attributeName, String attributeValue) throws PspException, DSMLProfileException, PspNoSuchIdentifierException { LOG.debug("Psp '{}' - Has attribute '{}' name '{}' value '{}'", new Object[] { getId(), PSPUtil.toString(psoID), attributeName, attributeValue, }); EqualityMatch equalityMatch = new EqualityMatch(attributeName, attributeValue); Filter filter = new Filter(); filter.setItem(equalityMatch); Query query = new Query(); query.setBasePsoID(psoID); query.setTargetID(psoID.getTargetID()); query.addQueryClause(filter); query.setScope(Scope.PSO); SearchRequest searchRequest = new SearchRequest(); searchRequest.setReturnData(ReturnData.IDENTIFIER); searchRequest.setQuery(query); searchRequest.setRequestID(PSPUtil.uniqueRequestId()); SearchResponse response = execute(searchRequest); if (!response.getStatus().equals(StatusCode.SUCCESS)) { String errorMessage = PSPUtil.toString(response); LOG.error(errorMessage); if (response.getStatus().equals(StatusCode.FAILURE) && response.getError().equals(ErrorCode.NO_SUCH_IDENTIFIER)) { throw new PspNoSuchIdentifierException(errorMessage); } throw new PspException(errorMessage); } if (response.getPSOs().length > 0) { LOG.debug("Psp '{}' - Has attribute true '{}' name '{}' value '{}'", new Object[] { getId(), PSPUtil.toString(psoID), attributeName, attributeValue, }); return true; } LOG.debug("Psp '{}' - Has attribute false '{}' name '{}' value '{}'", new Object[] { getId(), PSPUtil.toString(psoID), attributeName, attributeValue, }); return false; } /** * Return true if the given {@link PSOIdentifier} has the given {@Reference}. * * @param psoID the pso identifier * @param reference the reference * @return true if the pso identifier has the reference, false otherwise * @throws PspException if the spml search fails * @throws PspNoSuchIdentifierException if the psoID can not be found */ public boolean hasReference(PSOIdentifier psoID, Reference reference) throws PspException, PspNoSuchIdentifierException { LOG.debug("Psp '{}' - Has reference from '{}' to '{}'", new Object[] { getId(), PSPUtil.toString(psoID), PSPUtil.toString(reference), }); HasReference hasReference = new HasReference(); hasReference.setToPsoID(reference.getToPsoID()); hasReference.setTypeOfReference(reference.getTypeOfReference()); hasReference.setReferenceData(reference.getReferenceData()); Query query = new Query(); query.setBasePsoID(psoID); query.setTargetID(psoID.getTargetID()); query.addQueryClause(hasReference); query.setScope(Scope.PSO); // SPML library namespace marshalling issue // SearchRequest searchRequest = new SearchRequest(); SearchRequest searchRequest = new SearchRequestWithQueryClauseNamespaces(); searchRequest.setReturnData(ReturnData.IDENTIFIER); searchRequest.setQuery(query); searchRequest.setRequestID(PSPUtil.uniqueRequestId()); SearchResponse response = execute(searchRequest); if (!response.getStatus().equals(StatusCode.SUCCESS)) { String errorMessage = "Psp '" + getId() + "' - Has reference from '" + PSPUtil.toString(psoID) + "' to '" + PSPUtil.toString(reference) + "' " + PSPUtil.toString(response); LOG.error(errorMessage); if (response.getStatus().equals(StatusCode.FAILURE) && response.getError().equals(ErrorCode.NO_SUCH_IDENTIFIER)) { throw new PspNoSuchIdentifierException(errorMessage); } throw new PspException(errorMessage); } if (response.getPSOs().length > 0) { LOG.debug("Psp '{}' - Has reference true from '{}' to '{}'", new Object[] { getId(), PSPUtil.toString(psoID), PSPUtil.toString(reference), }); return true; } LOG.debug("Psp '{}' - Has reference false from '{}' to '{}'", new Object[] { getId(), PSPUtil.toString(psoID), PSPUtil.toString(reference), }); return false; } /** * {@inheritDoc} * * Initialize the SPMLv2 toolkit marshallers/unmarshallers. */ public void initialize() throws ServiceException { super.initialize(); this.initializeSpmlToolkit(); } /** * Initialize the SPMLv2 toolkit marshallers/unmarshallers. * * {@link PspMarshallableCreator} {@link DSMLUnmarshaller} */ public void initializeSpmlToolkit() { ObjectFactory.getInstance().addCreator(new PspMarshallableCreator()); ObjectFactory.getInstance().addOCEUnmarshaller(new DSMLUnmarshaller()); } /** * {@inheritDoc} * * Initialize the "objects" map whose keys are target ids and values are {@link Pso}s. Initialize the "targets" map * whose keys are target ids and values are {@link SpmlTarget}s. */ protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException { Map<String, List<Pso>> oldPsoDefinitions = objects; Map<String, SpmlTarget> oldTargets = targets; try { String[] psoBeanNames = newServiceContext.getBeanNamesForType(Pso.class); LOG.debug("PSP '{}' - Loading {} PSO definitions", getId(), Arrays.asList(psoBeanNames)); objects = new LinkedHashMap<String, List<Pso>>(psoBeanNames.length); for (String beanName : psoBeanNames) { Pso psoDefinition = (Pso) newServiceContext.getBean(beanName); String targetId = psoDefinition.getPsoIdentifier().getTargetId(); if (!objects.containsKey(targetId)) { objects.put(targetId, new ArrayList<Pso>()); } objects.get(targetId).add(psoDefinition); } targets = new LinkedHashMap<String, SpmlTarget>(objects.keySet().size()); for (String targetId : objects.keySet()) { Object target = newServiceContext.getBean(targetId, SpmlTarget.class); ((SpmlTarget) target).setPSP(this); targets.put(targetId, (SpmlTarget) target); } } catch (Exception e) { objects = oldPsoDefinitions; targets = oldTargets; LOG.error("PSP '" + getId() + "' - Configuration is not valid, retaining old configuration", e); throw new ServiceException( "PSP '" + getId() + "' - Configuration is not valid, retaining old configuration", e); } } /** * Returns a {@link ModifyRequest} suitable for renaming the {@PSOIdentifier} of the given * {@link PSO} . Returns null if the object can not or should not be renamed. * * The modify request must contain one and only one {@link AlternateIdentifier}. This alternate identifier is the * "old" or "current" identifier. * * The "new" identifier is the pso identifier of the given pso. * * If the "new" identifier already exists, or the "old" identifier does not exist, then return null. * * @param pso the provisioned object * @return the modify request or null if the object should not or can not be renamed * @throws PspException */ public ModifyRequest renameRequest(PSO pso) throws PspException { LOG.debug("PSP '{}' - Rename {}", getId(), PSPUtil.toString(pso)); // get alternate identifiers from correct pso List<AlternateIdentifier> oldIDs = pso.getOpenContentElements(AlternateIdentifier.class); // do not rename if there are no alternate identifiers if (oldIDs.isEmpty()) { LOG.debug("PSP '{}' - Rename {} Will not rename. One alternate identifier is required.", getId(), PSPUtil.toString(pso)); return null; } // throw exception if more than one alternate identifier if (oldIDs.size() > 1) { LOG.warn("PSP '{}' - Rename {} Will not rename. Multiple alternate identifiers are not supported.", getId(), PSPUtil.toString(pso)); return null; } PSOIdentifier newPSOID = pso.getPsoID(); // Do nothing if identifier exists. if (doesIdentifierExist(newPSOID)) { LOG.warn("PSP '{}' - Rename {} Will not rename. New identifier already exists.", getId(), PSPUtil.toString(pso)); return null; } // Lookup alternate identifiers to see if they exist. List<AlternateIdentifier> foundOldIDs = new ArrayList<AlternateIdentifier>(); for (AlternateIdentifier oldID : oldIDs) { if (doesIdentifierExist(oldID.getPSOIdentifier())) { foundOldIDs.add(oldID); } } // Do nothing if old identifiers do not exist. if (foundOldIDs.isEmpty()) { LOG.warn("PSP '{}' - Rename {} Will not rename. Alternate identifier must exist on target.", getId(), PSPUtil.toString(pso)); return null; } // Throw exception if more than one old identifier is found. if (foundOldIDs.size() > 1) { LOG.warn("PSP '{}' - Rename {} Will not rename. Multiple alternate identifiers exist on target.", getId(), PSPUtil.toString(pso)); return null; } PSOIdentifier oldPSOID = foundOldIDs.get(0).getPSOIdentifier(); // rename ModifyRequest modifyRequest = new ModifyRequest(); modifyRequest.setPsoID(oldPSOID); modifyRequest.setRequestID(PSPUtil.uniqueRequestId()); Modification modification = new Modification(); AlternateIdentifier newPsoID = new AlternateIdentifier(); newPsoID.setPSOIdentifier(newPSOID); modification.addOpenContentElement(newPsoID); modification.setModificationMode(ModificationMode.REPLACE); modifyRequest.addModification(modification); LOG.warn("PSP '{}' - Rename {} Will rename with modify request {}", new Object[] { getId(), PSPUtil.toString(pso), PSPUtil.toString(modifyRequest) }); return modifyRequest; } /** * Set the attribute authority * * @param attributeAuthority the attribute authority */ public void setAttributeAuthority(AttributeAuthority attributeAuthority) { this.attributeAuthority = attributeAuthority; } /** {@inheritDoc} */ public void setId(String id) { this.id = id; } /** * @param pspOptions The pspOptions to set. */ public void setPspOptions(PspOptions pspOptions) { this.pspOptions = pspOptions; } /** * Return a <code>ModifyRequest</code> for every data <code>Modification</code>. * * @param dataMods the <code>Modification</code>s * @param psoID the PSO Identifier * @param entityName the schema entity name * @return the <code>ModifyRequest</code>s * @throws Spml2Exception if an error occurs creating the <code>DSMLModification</code>s * */ public List<ModifyRequest> unbundleDataModifications(List<Modification> dataMods, PSOIdentifier psoID, String entityName) throws Spml2Exception { List<ModifyRequest> unbundledModifyRequests = new ArrayList<ModifyRequest>(); for (Modification modification : dataMods) { for (Object object : modification.getOpenContentElements(DSMLModification.class)) { DSMLModification dsmlModification = (DSMLModification) object; DSMLValue[] dsmlValues = dsmlModification.getValues(); for (DSMLValue dsmlValue : dsmlValues) { ModifyRequest unbundledModifyRequest = new ModifyRequest(); unbundledModifyRequest.setRequestID(PSPUtil.uniqueRequestId()); unbundledModifyRequest.setPsoID(psoID); if (entityName != null) { unbundledModifyRequest.addOpenContentAttr(Pso.ENTITY_NAME_ATTRIBUTE, entityName); } DSMLModification dsmlMod = new DSMLModification(dsmlModification.getName(), new DSMLValue[] { dsmlValue }, dsmlModification.getOperation()); Modification unbundledModification = new Modification(); unbundledModification.setModificationMode(modification.getModificationMode()); unbundledModification.addOpenContentElement(dsmlMod); unbundledModifyRequest.addModification(unbundledModification); unbundledModifyRequests.add(unbundledModifyRequest); } } } return unbundledModifyRequests; } /** * Return a <code>ModifyRequest</code> for every reference capability data <code>Modification</code>. * * @param referenceMods the <code>Modification</code>s * @param psoID the PSO Identifier * @param entityName the schema entity name * @return the <code>ModifyRequest</code>s * @throws Spml2Exception if an error occurs creating the dsml modifications * @throws PspException if the reference capability can not be handled */ public List<ModifyRequest> unbundleReferenceModifications(List<Modification> referenceMods, PSOIdentifier psoID, String entityName) throws Spml2Exception, PspException { List<ModifyRequest> unbundledModifyRequests = new ArrayList<ModifyRequest>(); for (Modification modification : referenceMods) { Map<String, List<Reference>> references = PSPUtil.getReferences(modification.getCapabilityData()); for (String typeOfReference : references.keySet()) { for (Reference reference : references.get(typeOfReference)) { ModifyRequest unbundledModifyRequest = new ModifyRequest(); unbundledModifyRequest.setRequestID(PSPUtil.uniqueRequestId()); unbundledModifyRequest.setPsoID(psoID); if (entityName != null) { unbundledModifyRequest.addOpenContentAttr(Pso.ENTITY_NAME_ATTRIBUTE, entityName); } CapabilityData capabilityData = PSPUtil .fromReferences(Arrays.asList(new Reference[] { reference })); Modification unbundledModification = new Modification(); unbundledModification.addCapabilityData(capabilityData); unbundledModification.setModificationMode(modification.getModificationMode()); unbundledModifyRequest.addModification(unbundledModification); unbundledModifyRequests.add(unbundledModifyRequest); } } } return unbundledModifyRequests; } /** * {@inheritDoc} * * The target of the request must be a configured target of this psp. The entity name of the request must be a * configured pso of this psp. The dsml attributes of the request must be configured pso attributes of this psp. */ public void validate(AddRequest addRequest, AddResponse addResponse) { super.validate(addRequest, addResponse); if (addResponse.getStatus() != null && !addResponse.getStatus().equals(StatusCode.SUCCESS)) { return; } SpmlTarget target = targets.get(addRequest.getPsoID().getTargetID()); if (target == null) { fail(addResponse, ErrorCode.INVALID_IDENTIFIER); } // Not null nor empty via super.validate(); String entityName = addRequest.findOpenContentAttrValueByName(Pso.ENTITY_NAME_ATTRIBUTE); Pso psoDefinition = null; for (Pso psoDef : objects.get(addRequest.getPsoID().getTargetID())) { if (psoDef.getId().equals(entityName)) { psoDefinition = psoDef; } } if (psoDefinition == null) { fail(addResponse, ErrorCode.MALFORMED_REQUEST, "Invalid entity name."); return; } Map<String, DSMLAttr> dsmlAttrs = PSPUtil.getDSMLAttrMap(addRequest.getData()); for (String attrName : dsmlAttrs.keySet()) { if (psoDefinition.getPsoAttribute(attrName) == null) { fail(addResponse, ErrorCode.MALFORMED_REQUEST, "Unknown attribute."); return; } } } /** * Validate that the targets and objects of the request are known to this psp. * * @param provisioningRequest the bulk provisioning request * @param provisioningResponse the bulk provisioning response */ public void validate(BulkProvisioningRequest provisioningRequest, ProvisioningResponse provisioningResponse) { try { getTargetAndObjectDefinitions(provisioningRequest); } catch (PspException e) { fail(provisioningResponse, ErrorCode.NO_SUCH_IDENTIFIER, e.getMessage()); } } /** * {@inheritDoc} * * The target of the request must be a configured target of this PSP. */ public void validate(DeleteRequest deleteRequest, DeleteResponse deleteResponse) { super.validate(deleteRequest, deleteResponse); if (deleteResponse.getStatus() != null && !deleteResponse.getStatus().equals(StatusCode.SUCCESS)) { return; } SpmlTarget target = targets.get(deleteRequest.getPsoID().getTargetID()); if (target == null) { fail(deleteResponse, ErrorCode.INVALID_IDENTIFIER); } } /** * {@inheritDoc} * * The target of the request must be a configured target of this PSP. */ public void validate(LookupRequest lookupRequest, LookupResponse lookupResponse) { super.validate(lookupRequest, lookupResponse); if (lookupResponse.getStatus() != null && !lookupResponse.getStatus().equals(StatusCode.SUCCESS)) { return; } SpmlTarget target = targets.get(lookupRequest.getPsoID().getTargetID()); if (target == null) { fail(lookupResponse, ErrorCode.INVALID_IDENTIFIER); } } /** * {@inheritDoc} * * The target of the modify request must be a configured target of this psp. The modify request must have at least * one modification whose mode is add, delete, or replace. If the modify request contains an alternate identifier, * only one alternate identifier may exist. */ public void validate(ModifyRequest modifyRequest, ModifyResponse modifyResponse) { super.validate(modifyRequest, modifyResponse); if (modifyResponse.getStatus() != null && !modifyResponse.getStatus().equals(StatusCode.SUCCESS)) { return; } SpmlTarget target = targets.get(modifyRequest.getPsoID().getTargetID()); if (target == null) { fail(modifyResponse, ErrorCode.INVALID_IDENTIFIER); } if (modifyRequest.getModifications().length == 0) { fail(modifyResponse, ErrorCode.MALFORMED_REQUEST, "A modification is required."); return; } for (Modification modification : modifyRequest.getModifications()) { if (modification.getModificationMode() == null) { fail(modifyResponse, ErrorCode.MALFORMED_REQUEST, "A modification mode is required."); return; } if (!(modification.getModificationMode().equals(ModificationMode.ADD) || modification.getModificationMode().equals(ModificationMode.DELETE) || modification.getModificationMode().equals(ModificationMode.REPLACE))) { fail(modifyResponse, ErrorCode.MALFORMED_REQUEST, "Unsupported modification mode."); return; } } for (Modification modification : modifyRequest.getModifications()) { List<AlternateIdentifier> alternateIdentifiers = modification .getOpenContentElements(AlternateIdentifier.class); if (alternateIdentifiers.size() > 1) { fail(modifyResponse, ErrorCode.CUSTOM_ERROR, "Only one alternate identifier is supported."); } if (alternateIdentifiers.size() == 1) { AlternateIdentifier alternateIdentifier = alternateIdentifiers.get(0); if (alternateIdentifier.getID() == null || alternateIdentifier.getID().length() == 0) { fail(modifyResponse, ErrorCode.MALFORMED_REQUEST); } if (alternateIdentifier.getTargetID() == null || alternateIdentifier.getTargetID().length() == 0) { fail(modifyResponse, ErrorCode.MALFORMED_REQUEST); } } } } /** * The provisioning request must have an id and the target id and entity name must be known to this psp. * * @param provisioningRequest the provisioning request * @param provisioningResponse the provisioning response */ public void validate(ProvisioningRequest provisioningRequest, ProvisioningResponse provisioningResponse) { if (DatatypeHelper.isEmpty(provisioningRequest.getId())) { fail(provisioningResponse, ErrorCode.MALFORMED_REQUEST); } try { getTargetAndObjectDefinitions(provisioningRequest); } catch (PspException e) { fail(provisioningResponse, ErrorCode.NO_SUCH_IDENTIFIER, e.getMessage()); } } /** * {@inheritDoc} * * The target of the request must be a configured target of this PSP. */ public void validate(SearchRequest searchRequest, SearchResponse searchResponse) { super.validate(searchRequest, searchResponse); if (searchResponse.getStatus() != null && !searchResponse.getStatus().equals(StatusCode.SUCCESS)) { return; } SpmlTarget target = targets.get(searchRequest.getQuery().getTargetID()); if (target == null) { fail(searchResponse, ErrorCode.NO_SUCH_IDENTIFIER); return; } } }