/*
* 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.*;
import com.evolveum.midpoint.model.common.util.AbstractModelWebService;
import com.evolveum.midpoint.prism.*;
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.*;
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.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.common.fault_3.*;
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;
@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 = modelService.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.parserFor(scriptsAsString).xml().parseItem();
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) {
Object scriptValue = script.getValue();
if (!(scriptValue instanceof ScriptingExpressionType)) {
throw new SchemaException("Expected that scripts will be of type ScriptingExpressionType, but it was "+scriptValue.getClass().getName());
}
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.xmlSerializer().serializeAnyData(jaxbOutput, SchemaConstants.C_VALUE));
}
}
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<PrismValue> output) throws JAXBException, SchemaException {
ItemListType itemListType = new ItemListType();
if (output != null) {
for (PrismValue value: output) {
RawType rawType = new RawType(prismContext.xnodeSerializer().root(SchemaConstants.C_VALUE).serialize(value), prismContext);
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();
}
}