com.evolveum.midpoint.model.impl.ModelWebService.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.impl.ModelWebService.java

Source

/*
 * Copyright (c) 2010-2013 Evolveum
 *
 * Licensed 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 com.evolveum.midpoint.model.impl;

import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ModelPort;
import com.evolveum.midpoint.model.api.PolicyViolationException;
import com.evolveum.midpoint.model.api.ScriptExecutionResult;
import com.evolveum.midpoint.model.api.ScriptingService;
import com.evolveum.midpoint.model.common.util.AbstractModelWebService;
import com.evolveum.midpoint.model.impl.controller.ModelController;
import com.evolveum.midpoint.model.impl.scripting.Data;
import com.evolveum.midpoint.model.impl.scripting.ExecutionContext;
import com.evolveum.midpoint.model.api.ScriptExecutionException;
import com.evolveum.midpoint.model.impl.scripting.ScriptingExpressionEvaluator;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.QueryJaxbConvertor;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.AuthorizationException;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ExecuteScriptsOptionsType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectDeltaListType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectDeltaOperationListType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectListType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.OutputFormatType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ScriptOutputsType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.SelectorQualifiedGetOptionsType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.SingleScriptOutputType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ModelExecuteOptionsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectDeltaOperationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectShadowChangeDescriptionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.CommunicationFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.ConfigurationFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.FaultMessage;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.FaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.IllegalArgumentFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.ObjectAlreadyExistsFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.ObjectNotFoundFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.PolicyViolationFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.SchemaViolationFaultType;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.SystemFaultType;
import com.evolveum.midpoint.xml.ns._public.model.model_3.ExecuteScriptsResponseType;
import com.evolveum.midpoint.xml.ns._public.model.model_3.ExecuteScriptsType;
import com.evolveum.midpoint.xml.ns._public.model.model_3.ModelPortType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ItemListType;
import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType;
import com.evolveum.prism.xml.ns._public.query_3.QueryType;
import com.evolveum.prism.xml.ns._public.types_3.RawType;

import org.apache.commons.lang.StringUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.ws.Holder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 
 * @author lazyman
 * 
 */
@Service
public class ModelWebService extends AbstractModelWebService implements ModelPortType, ModelPort {

    private static final Trace LOGGER = TraceManager.getTrace(ModelWebService.class);

    @Autowired(required = true)
    private ModelCrudService model;

    // for more complicated interactions (like executeChanges)
    @Autowired
    private ModelController modelController;

    @Autowired
    private ScriptingService scriptingService;

    @Override
    public void getObject(QName objectType, String oid, SelectorQualifiedGetOptionsType optionsType,
            Holder<ObjectType> objectHolder, Holder<OperationResultType> resultHolder) throws FaultMessage {
        notNullArgument(objectType, "Object type must not be null.");
        notEmptyArgument(oid, "Oid must not be null or empty.");

        Task task = createTaskInstance(GET_OBJECT);
        auditLogin(task);
        OperationResult operationResult = task.getResult();
        try {
            Class objectClass = ObjectTypes.getObjectTypeFromTypeQName(objectType).getClassDefinition();
            Collection<SelectorOptions<GetOperationOptions>> options = MiscSchemaUtil
                    .optionsTypeToOptions(optionsType);
            PrismObject<? extends ObjectType> object = model.getObject(objectClass, oid, options, task,
                    operationResult);
            handleOperationResult(operationResult, resultHolder);
            objectHolder.value = object.asObjectable();
            return;
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL getObject() failed", ex);
            throwFault(ex, operationResult);
        } finally {
            auditLogout(task);
        }
    }

    @Override
    public void searchObjects(QName objectType, QueryType query, SelectorQualifiedGetOptionsType optionsType,
            Holder<ObjectListType> objectListHolder, Holder<OperationResultType> result) throws FaultMessage {
        notNullArgument(objectType, "Object type must not be null.");

        Task task = createTaskInstance(SEARCH_OBJECTS);
        auditLogin(task);
        OperationResult operationResult = task.getResult();
        try {
            Class objectClass = ObjectTypes.getObjectTypeFromTypeQName(objectType).getClassDefinition();
            Collection<SelectorOptions<GetOperationOptions>> options = MiscSchemaUtil
                    .optionsTypeToOptions(optionsType);
            ObjectQuery q = QueryJaxbConvertor.createObjectQuery(objectClass, query, prismContext);
            List<PrismObject<? extends ObjectType>> list = (List) model.searchObjects(objectClass, q, options, task,
                    operationResult);
            handleOperationResult(operationResult, result);
            ObjectListType listType = new ObjectListType();
            for (PrismObject<? extends ObjectType> o : list) {
                listType.getObject().add(o.asObjectable());
            }
            objectListHolder.value = listType;
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL searchObjects() failed", ex);
            throwFault(ex, operationResult);
        } finally {
            auditLogout(task);
        }
    }

    @Override
    public ObjectDeltaOperationListType executeChanges(ObjectDeltaListType deltaList,
            ModelExecuteOptionsType optionsType) throws FaultMessage {
        notNullArgument(deltaList, "Object delta list must not be null.");

        Task task = createTaskInstance(EXECUTE_CHANGES);
        auditLogin(task);
        OperationResult operationResult = task.getResult();
        try {
            Collection<ObjectDelta> deltas = DeltaConvertor.createObjectDeltas(deltaList, prismContext);
            for (ObjectDelta delta : deltas) {
                prismContext.adopt(delta);
            }
            ModelExecuteOptions options = ModelExecuteOptions.fromModelExecutionOptionsType(optionsType);
            Collection<ObjectDeltaOperation<? extends ObjectType>> objectDeltaOperations = modelController
                    .executeChanges((Collection) deltas, options, task, operationResult); // brutally eliminating type-safety compiler barking
            ObjectDeltaOperationListType retval = new ObjectDeltaOperationListType();
            for (ObjectDeltaOperation objectDeltaOperation : objectDeltaOperations) {
                ObjectDeltaOperationType objectDeltaOperationType = DeltaConvertor
                        .toObjectDeltaOperationType(objectDeltaOperation, null);
                retval.getDeltaOperation().add(objectDeltaOperationType);
            }
            return retval;
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL executeChanges() failed", ex);
            throwFault(ex, operationResult);
            // notreached
            return null;
        } finally {
            auditLogout(task);
        }
    }

    @Override
    public void findShadowOwner(String accountOid, Holder<UserType> userHolder, Holder<OperationResultType> result)
            throws FaultMessage {
        notEmptyArgument(accountOid, "Account oid must not be null or empty.");

        Task task = createTaskInstance(LIST_ACCOUNT_SHADOW_OWNER);
        auditLogin(task);
        OperationResult operationResult = task.getResult();
        try {
            PrismObject<UserType> user = model.findShadowOwner(accountOid, task, operationResult);
            handleOperationResult(operationResult, result);
            if (user != null) {
                userHolder.value = user.asObjectable();
            }
            return;
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL findShadowOwner() failed", ex);
            throwFault(ex, operationResult);
        } finally {
            auditLogout(task);
        }
    }

    @Override
    public OperationResultType testResource(String resourceOid) throws FaultMessage {
        notEmptyArgument(resourceOid, "Resource oid must not be null or empty.");

        Task task = createTaskInstance(TEST_RESOURCE);
        auditLogin(task);
        try {
            OperationResult testResult = model.testResource(resourceOid, task);
            return handleOperationResult(testResult);
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL testResource() failed", ex);
            OperationResult faultResult = new OperationResult(TEST_RESOURCE);
            faultResult.recordFatalError(ex);
            throwFault(ex, faultResult);
            // notreached
            return null;
        } finally {
            auditLogout(task);
        }
    }

    @Override
    public ExecuteScriptsResponseType executeScripts(ExecuteScriptsType parameters) throws FaultMessage {
        Task task = createTaskInstance(EXECUTE_SCRIPTS);
        auditLogin(task);
        OperationResult result = task.getResult();
        try {
            List<JAXBElement<?>> scriptsToExecute = parseScripts(parameters);
            return doExecuteScripts(scriptsToExecute, parameters.getOptions(), task, result);
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL executeScripts() failed", ex);
            throwFault(ex, null);
            // notreached
            return null;
        } finally {
            auditLogout(task);
        }
    }

    private List<JAXBElement<?>> parseScripts(ExecuteScriptsType parameters) throws JAXBException, SchemaException {
        List<JAXBElement<?>> scriptsToExecute = new ArrayList<>();
        if (parameters.getXmlScripts() != null) {
            for (Object scriptAsObject : parameters.getXmlScripts().getAny()) {
                if (scriptAsObject instanceof JAXBElement) {
                    scriptsToExecute.add((JAXBElement) scriptAsObject);
                } else {
                    throw new IllegalArgumentException("Invalid script type: " + scriptAsObject.getClass());
                }
            }
        } else {
            // here comes MSL script decoding (however with a quick hack to allow passing XML as text here)
            String scriptsAsString = parameters.getMslScripts();
            if (scriptsAsString.startsWith("<?xml")) {
                PrismProperty expressionType = (PrismProperty) prismContext.parseAnyData(scriptsAsString,
                        PrismContext.LANG_XML);
                if (expressionType.size() != 1) {
                    throw new IllegalArgumentException("Unexpected number of scripting expressions at input: "
                            + expressionType.size() + " (expected 1)");
                }
                scriptsToExecute.add(expressionType.getAnyValue().toJaxbElement());
            }
        }
        return scriptsToExecute;
    }

    private ExecuteScriptsResponseType doExecuteScripts(List<JAXBElement<?>> scriptsToExecute,
            ExecuteScriptsOptionsType options, Task task, OperationResult result) {
        ExecuteScriptsResponseType response = new ExecuteScriptsResponseType();
        ScriptOutputsType outputs = new ScriptOutputsType();
        response.setOutputs(outputs);

        try {
            for (JAXBElement<?> script : scriptsToExecute) {

                ScriptExecutionResult executionResult = scriptingService
                        .evaluateExpression((ScriptingExpressionType) script.getValue(), task, result);

                SingleScriptOutputType output = new SingleScriptOutputType();
                outputs.getOutput().add(output);

                output.setTextOutput(executionResult.getConsoleOutput());
                if (options == null || options.getOutputFormat() == null
                        || options.getOutputFormat() == OutputFormatType.XML) {
                    output.setXmlData(prepareXmlData(executionResult.getDataOutput()));
                } else {
                    // temporarily we send serialized XML in the case of MSL output
                    ItemListType jaxbOutput = prepareXmlData(executionResult.getDataOutput());
                    output.setMslData(prismContext.serializeAnyData(jaxbOutput, SchemaConstants.C_VALUE,
                            PrismContext.LANG_XML));
                }
            }
            result.computeStatusIfUnknown();
        } catch (ScriptExecutionException | JAXBException | SchemaException | RuntimeException
                | SecurityViolationException e) {
            result.recordFatalError(e.getMessage(), e);
            LoggingUtils.logException(LOGGER, "Exception while executing script", e);
        }
        result.summarize();
        response.setResult(result.createOperationResultType());
        return response;
    }

    private ItemListType prepareXmlData(List<Item> output) throws JAXBException, SchemaException {
        ItemListType itemListType = new ItemListType();
        if (output != null) {
            for (Item item : output) {
                RawType rawType = prismContext.toRawType(item);
                itemListType.getItem().add(rawType);
            }
        }
        return itemListType;
    }

    private void handleOperationResult(OperationResult result, Holder<OperationResultType> holder) {
        result.recordSuccess();
        OperationResultType resultType = result.createOperationResultType();
        if (holder.value == null) {
            holder.value = resultType;
        } else {
            holder.value.getPartialResults().add(resultType);
        }
    }

    private OperationResultType handleOperationResult(OperationResult result) {
        result.recordSuccess();
        return result.createOperationResultType();
    }

    private void notNullResultHolder(Holder<OperationResultType> holder) throws FaultMessage {
        notNullArgument(holder, "Holder must not be null.");
        notNullArgument(holder.value, "Result type must not be null.");
    }

    private <T> void notNullHolder(Holder<T> holder) throws FaultMessage {
        notNullArgument(holder, "Holder must not be null.");
        notNullArgument(holder.value, holder.getClass().getSimpleName() + " must not be null (in Holder).");
    }

    private void notEmptyArgument(String object, String message) throws FaultMessage {
        if (StringUtils.isEmpty(object)) {
            throw createIllegalArgumentFault(message);
        }
    }

    private void notNullArgument(Object object, String message) throws FaultMessage {
        if (object == null) {
            throw createIllegalArgumentFault(message);
        }
    }

    public FaultMessage createIllegalArgumentFault(String message) {
        FaultType faultType = new IllegalArgumentFaultType();
        return new FaultMessage(message, faultType);
    }

    public void throwFault(Exception ex, OperationResult result) throws FaultMessage {
        if (result != null) {
            result.recordFatalError(ex.getMessage(), ex);
        }

        FaultType faultType;
        if (ex instanceof ObjectNotFoundException) {
            faultType = new ObjectNotFoundFaultType();
        } else if (ex instanceof IllegalArgumentException) {
            faultType = new IllegalArgumentFaultType();
        } else if (ex instanceof ObjectAlreadyExistsException) {
            faultType = new ObjectAlreadyExistsFaultType();
        } else if (ex instanceof CommunicationException) {
            faultType = new CommunicationFaultType();
        } else if (ex instanceof ConfigurationException) {
            faultType = new ConfigurationFaultType();
        } else if (ex instanceof ExpressionEvaluationException) {
            faultType = new SystemFaultType();
        } else if (ex instanceof SchemaException) {
            faultType = new SchemaViolationFaultType();
        } else if (ex instanceof PolicyViolationException) {
            faultType = new PolicyViolationFaultType();
        } else if (ex instanceof AuthorizationException) {
            throw new Fault(new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION),
                    WSSecurityException.ErrorCode.FAILED_AUTHENTICATION.getQName());
        } else if (ex instanceof SecurityViolationException) {
            throw new Fault(new WSSecurityException(WSSecurityException.ErrorCode.FAILURE),
                    WSSecurityException.ErrorCode.FAILURE.getQName());
        } else {
            faultType = new SystemFaultType();
        }
        faultType.setMessage(ex.getMessage());
        if (result != null) {
            faultType.setOperationResult(result.createOperationResultType());
        }

        FaultMessage fault = new FaultMessage(ex.getMessage(), faultType, ex);
        LOGGER.trace("Throwing fault message type: {}", faultType.getClass(), fault);
        throw fault;
    }

    @Override
    public TaskType importFromResource(String resourceOid, QName objectClass) throws FaultMessage {
        notEmptyArgument(resourceOid, "Resource oid must not be null or empty.");
        notNullArgument(objectClass, "Object class must not be null.");

        Task task = createTaskInstance(IMPORT_FROM_RESOURCE);
        auditLogin(task);
        OperationResult operationResult = task.getResult();

        try {
            model.importFromResource(resourceOid, objectClass, task, operationResult);
            operationResult.computeStatus();
            return handleTaskResult(task);
        } catch (Exception ex) {
            LoggingUtils.logException(LOGGER, "# MODEL importFromResource() failed", ex);
            auditLogout(task);
            throwFault(ex, operationResult);
            // notreached
            return null;
        }
    }

    @Override
    public TaskType notifyChange(ResourceObjectShadowChangeDescriptionType changeDescription) throws FaultMessage {
        // TODO Auto-generated method stub
        notNullArgument(changeDescription, "Change description must not be null");
        LOGGER.trace("notify change started");

        Task task = createTaskInstance(NOTIFY_CHANGE);
        OperationResult parentResult = task.getResult();

        try {
            model.notifyChange(changeDescription, parentResult, task);
        } catch (ObjectNotFoundException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        } catch (SchemaException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        } catch (CommunicationException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        } catch (ConfigurationException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        } catch (SecurityViolationException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        } catch (RuntimeException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        } catch (ObjectAlreadyExistsException ex) {
            LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex);
            auditLogout(task);
            throwFault(ex, parentResult);
        }

        LOGGER.info("notify change ended.");
        LOGGER.info("result of notify change: {}", parentResult.debugDump());
        return handleTaskResult(task);
    }

    /**
     * return appropriate form of taskType (and result) to
     * return back to a web service caller.
     * 
     * @param task
     */
    private TaskType handleTaskResult(Task task) {
        return task.getTaskPrismObject().asObjectable();
    }

}