com.evolveum.midpoint.testing.model.client.sample.AbstractTestForExchangeConnector.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.testing.model.client.sample.AbstractTestForExchangeConnector.java

Source

/*
 * Copyright (c) 2010-2014 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.testing.model.client.sample;

import com.evolveum.midpoint.model.client.ModelClientUtil;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.GetOperationOptionsType;
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.ObjectSelectorType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.RetrieveOptionType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.SelectorQualifiedGetOptionType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.SelectorQualifiedGetOptionsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType;
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.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAttributesType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType;
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.FaultMessage;
import com.evolveum.midpoint.xml.ns._public.model.model_3.ModelPortType;
import com.evolveum.midpoint.xml.ns._public.model.model_3.ModelService;
import com.evolveum.prism.xml.ns._public.query_3.OrderDirectionType;
import com.evolveum.prism.xml.ns._public.query_3.PagingType;
import com.evolveum.prism.xml.ns._public.query_3.QueryType;
import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType;
import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType;
import com.evolveum.prism.xml.ns._public.types_3.ItemDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;
import com.evolveum.prism.xml.ns._public.types_3.ModificationTypeType;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import org.testng.AssertJUnit;
import org.testng.annotations.BeforeClass;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Holder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Common functionality for connector-related tests.
 *
 * @author mederly
 */
public class AbstractTestForExchangeConnector {

    // Configuration
    public static final String ADM_USERNAME = "administrator";
    public static final String ADM_PASSWORD = "5ecr3t";
    public static final String DEFAULT_ENDPOINT_URL = "http://localhost.:8080/midpoint/model/model-3";

    // Object OIDs

    // Other
    public static final String NS_RI = "http://midpoint.evolveum.com/xml/ns/public/resource/instance-3";
    public static final String NS_ICFS = "http://midpoint.evolveum.com/xml/ns/public/connector/icf-1/resource-schema-3";

    public static final QName OC_ACCOUNT = new QName(NS_RI, "AccountObjectClass");
    public static final QName OC_ACCEPTED_DOMAIN = new QName(NS_RI, "CustomAcceptedDomainObjectClass");
    public static final QName OC_GLOBAL_ADDRESS_LIST = new QName(NS_RI, "CustomGlobalAddressListObjectClass");
    public static final QName OC_ADDRESS_LIST = new QName(NS_RI, "CustomAddressListObjectClass");
    public static final QName OC_OFFLINE_ADDRESS_BOOK = new QName(NS_RI, "CustomOfflineAddressBookObjectClass");
    public static final QName OC_ADDRESS_BOOK_POLICY = new QName(NS_RI, "CustomAddressBookPolicyObjectClass");
    public static final QName OC_DISTRIBUTION_GROUP = new QName(NS_RI, "CustomDistributionGroupObjectClass");
    public static final QName OC_EMAIL_ADDRESS_POLICY = new QName(NS_RI, "CustomEmailAddressPolicyObjectClass");

    // objects created (to be cleaned up at the end)
    protected List<String> acceptedDomains = new ArrayList<>();
    protected List<String> globalAddressLists = new ArrayList<>();
    protected List<String> addressLists = new ArrayList<>();
    protected List<String> offlineAddressBooks = new ArrayList<>();
    protected List<String> addressBookPolicies = new ArrayList<>();
    protected List<String> distributionGroups = new ArrayList<>();
    protected List<String> emailAddressPolicies = new ArrayList<>();
    protected List<OrgType> orgs = new ArrayList<>();

    protected ModelPortType modelPort;
    protected ObjectDeltaOperationType lastOdo; // a hack to see last result

    //    private String dn(String givenName, String sn) {
    //        return "CN=" + givenName + " " + sn + "," + getContainer();
    //    }
    //
    //    private String mail(String givenName, String sn) {
    //        return sn.toLowerCase() + "@" + getMailDomain();
    //    }
    //
    //    private String getContainer() {
    //        return System.getProperty("container");
    //    }
    //
    protected String getResourceOid() {
        return System.getProperty("resourceOid");
    }
    //
    //    public String getMailDomain() {
    //        return System.getProperty("mailDomain");
    //    }

    @BeforeClass
    public void initialize() throws Exception {
        modelPort = createModelPort(new String[0]);
    }

    // =============== AcceptedDomain ===============

    protected String createAndCheckAcceptedDomain(String name, String domainName, String domainType)
            throws Exception {
        System.out.println("Creating accepted domain " + name);
        String oid = createAcceptedDomain(name, domainName, domainType);
        System.out.println("Done; OID = " + oid);
        acceptedDomains.add(oid);
        return checkAcceptedDomain(name, domainName, domainType).getOid();
    }

    protected String createAcceptedDomain(String name, String domainName, String domainType) throws FaultMessage {

        Document doc = ModelClientUtil.getDocumnent();

        ShadowType shadow = new ShadowType();
        shadow.setName(ModelClientUtil.createPolyStringType(name, doc));
        shadow.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        shadow.setObjectClass(OC_ACCEPTED_DOMAIN);
        shadow.setKind(ShadowKindType.GENERIC);
        shadow.setIntent("custom-accepted-domain");

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "DomainName"), domainName, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "DomainType"), domainType, doc));

        shadow.setAttributes(attributes);

        return createShadow(shadow);
    }

    protected ShadowType checkAcceptedDomain(String name, String domainName, String domainType) throws Exception {
        System.out.println("Retrieving AcceptedDomain " + name);
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_ACCEPTED_DOMAIN, name);
        AssertJUnit.assertNotNull("AcceptedDomain " + name + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "name", name);
        assertAttributeEquals(attrs, "DomainName", domainName);
        assertAttributeEquals(attrs, "DomainType", domainType);
        return shadowType;
    }

    // =============== GAL ===============

    protected String createAndCheckGlobalAddressList(String name, String valueToExpect) throws Exception {
        System.out.println("Creating GAL " + name);
        String oid = createGlobalAddressList(name, valueToExpect);
        System.out.println("Done; OID = " + oid);
        globalAddressLists.add(oid);
        return checkGlobalAddressList(name, galFilter(valueToExpect)).getOid();
    }

    protected String galFilter(String valueToExpect) {
        return "((Alias -ne $null) -and (CustomAttribute1 -eq '" + valueToExpect + "'))";
    }

    protected String createGlobalAddressList(String name, String valueToExpect) throws FaultMessage {

        Document doc = ModelClientUtil.getDocumnent();

        ShadowType shadow = new ShadowType();
        shadow.setName(ModelClientUtil.createPolyStringType(name, doc));
        shadow.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        shadow.setObjectClass(OC_GLOBAL_ADDRESS_LIST);
        shadow.setKind(ShadowKindType.GENERIC);
        shadow.setIntent("global-address-list");

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "RecipientFilter"),
                galFilter(valueToExpect), doc));
        shadow.setAttributes(attributes);

        return createShadow(shadow);
    }

    protected ShadowType checkGlobalAddressList(String name, String recipientFilter) throws Exception {
        System.out.println("Retrieving GAL " + name);
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_GLOBAL_ADDRESS_LIST, name);
        AssertJUnit.assertNotNull("GAL " + name + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "name", name);
        if (recipientFilter != null) {
            assertAttributeEquals(attrs, "RecipientFilter", recipientFilter);
        }
        return shadowType;
    }

    // =============== Address Lists ===============

    enum AddressListType {

        USERS("address-list-all-users",
                "((((Alias -ne $null) -and (CustomAttribute1 -eq '$$$'))) -and (ObjectCategory -like 'person'))"), GROUPS(
                        "address-list-all-groups",
                        "((((Alias -ne $null) -and (CustomAttribute1 -eq '$$$'))) -and (ObjectCategory -like 'group'))"), CONTACTS(
                                "address-list-all-contacts",
                                "((((Alias -ne $null) -and (CustomAttribute1 -eq '$$$'))) -and (((ObjectCategory -like 'person') -and (ObjectClass -eq 'contact'))))"), ROOMS(
                                        "address-list-all-rooms",
                                        "((((Alias -ne $null) -and (CustomAttribute1 -eq '$$$'))) -and (((RecipientDisplayType -eq 'ConferenceRoomMailbox') -or (RecipientDisplayType -eq 'SyncedConferenceRoomMailbox'))))");

        private String intent;
        private final String filterTemplate;

        AddressListType(String intent, String filterTemplate) {
            this.intent = intent;
            this.filterTemplate = filterTemplate;
        }

        public String createFilter(String valueToExpect) {
            return filterTemplate.replace("$$$", valueToExpect);
        }

        public String getIntent() {
            return intent;
        }
    }

    protected String createAndCheckAddressList(String name, String valueToExpect, AddressListType type)
            throws Exception {
        System.out.println("Creating AddressList " + name);
        String oid = createAddressList(name, valueToExpect, type);
        System.out.println("Done; OID = " + oid);
        addressLists.add(oid);
        return checkAddressList(name, type.createFilter(valueToExpect)).getOid();
    }

    protected String createAddressList(String name, String valueToExpect, AddressListType type)
            throws FaultMessage {

        Document doc = ModelClientUtil.getDocumnent();

        ShadowType shadow = new ShadowType();
        shadow.setName(ModelClientUtil.createPolyStringType(name, doc));
        shadow.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        shadow.setObjectClass(OC_ADDRESS_LIST);
        shadow.setKind(ShadowKindType.GENERIC);
        shadow.setIntent(type.getIntent());

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "RecipientFilter"),
                type.createFilter(valueToExpect), doc));
        shadow.setAttributes(attributes);

        return createShadow(shadow);
    }

    protected ShadowType checkAddressList(String name, String recipientFilter) throws Exception {
        System.out.println("Retrieving AddressList " + name);
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_ADDRESS_LIST, name);
        AssertJUnit.assertNotNull("AddressList " + name + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "name", name);
        assertAttributeEquals(attrs, "RecipientFilter", recipientFilter);
        return shadowType;
    }

    // =============== OfflineAddressBook ===============

    protected String createAndCheckOfflineAddressBook(String name, String addressList, String tenantName)
            throws Exception {
        System.out.println("Creating OAB " + name);
        String oid = createOfflineAddressBook(name, addressList, tenantName);
        System.out.println("Done; OID = " + oid);
        offlineAddressBooks.add(oid);
        return checkOfflineAddressBook(name, addressList).getOid();
    }

    protected String createOfflineAddressBook(String name, String addressList, String tenantName)
            throws FaultMessage {

        Document doc = ModelClientUtil.getDocumnent();

        ShadowType shadow = new ShadowType();
        shadow.setName(ModelClientUtil.createPolyStringType(name, doc));
        shadow.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        shadow.setObjectClass(OC_OFFLINE_ADDRESS_BOOK);
        shadow.setKind(ShadowKindType.GENERIC);
        shadow.setIntent("offline-address-book");

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        attributes.getAny()
                .add(ModelClientUtil.createTextElement(new QName(NS_RI, "AddressLists"), addressList, doc));
        //attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "_TenantName"), tenantName, doc));
        shadow.setAttributes(attributes);

        return createShadow(shadow);
    }

    protected ShadowType checkOfflineAddressBook(String name, String addressList) throws Exception {
        System.out.println("Retrieving OAB " + name);
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_OFFLINE_ADDRESS_BOOK, name);
        AssertJUnit.assertNotNull("OAB " + name + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "name", name);
        assertAttributeEquals(attrs, "AddressLists", "\\" + addressList);
        return shadowType;
    }

    // TODO checking access rights to download OAB

    // =============== AddressBookPolicy ===============

    protected String createAndCheckAddressBookPolicy(String name, Collection<String> addressLists, String gal,
            String oab, String rooms) throws Exception {
        System.out.println("Creating ABP " + name);
        String oid = createAddressBookPolicy(name, addressLists, gal, oab, rooms);
        System.out.println("Done; OID = " + oid);
        addressBookPolicies.add(oid);
        return checkAddressBookPolicy(name, addressLists, gal, oab, rooms).getOid();
    }

    protected String createAddressBookPolicy(String name, Collection<String> addressLists, String gal, String oab,
            String rooms) throws FaultMessage {

        Document doc = ModelClientUtil.getDocumnent();

        ShadowType shadow = new ShadowType();
        shadow.setName(ModelClientUtil.createPolyStringType(name, doc));
        shadow.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        shadow.setObjectClass(OC_ADDRESS_BOOK_POLICY);
        shadow.setKind(ShadowKindType.GENERIC);
        shadow.setIntent("address-book-policy");

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        for (String addressList : addressLists) {
            attributes.getAny()
                    .add(ModelClientUtil.createTextElement(new QName(NS_RI, "AddressLists"), addressList, doc));
        }
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "GlobalAddressList"), gal, doc));
        attributes.getAny()
                .add(ModelClientUtil.createTextElement(new QName(NS_RI, "OfflineAddressBook"), oab, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "RoomList"), rooms, doc));
        shadow.setAttributes(attributes);

        return createShadow(shadow);
    }

    protected ShadowType checkAddressBookPolicy(String name, Collection<String> addressLists, String gal,
            String oab, String rooms) throws Exception {
        System.out.println("Retrieving ABP " + name);
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_ADDRESS_BOOK_POLICY, name);
        AssertJUnit.assertNotNull("ABP " + name + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "name", name);
        assertAttributeEquals(attrs, "AddressLists", new HashSet<>(addressLists));
        assertAttributeEquals(attrs, "GlobalAddressList", gal);
        assertAttributeEquals(attrs, "OfflineAddressBook", oab);
        assertAttributeEquals(attrs, "RoomList", rooms);
        return shadowType;
    }

    // =============== DistributionGroup ===============

    protected String distributionGroupName(String name) {
        return "Mail-" + name + "@MailSecurity";
    }

    protected String distributionGroupPrimaryAddress(String name) {
        return "Mail-" + name + "@MailSecurity";
    }

    protected Collection<String> distributionGroupMembers(String name) {
        //return Arrays.asList("Mail-" + name + "@AllUsers");
        return new ArrayList<>();
    }

    protected String distributionGroupDisplayName(String name) {
        return "Mail-" + name + "@MailSecurity";
    }

    protected String createAndCheckDistributionGroup(String name, String primaryAddress, Collection<String> members,
            String ou, String displayName, String valueForFilter) throws Exception {
        System.out.println("Creating DG " + name);
        String oid = createDistributionGroup(name, primaryAddress, members, ou, displayName, valueForFilter);
        System.out.println("Done; OID = " + oid);
        distributionGroups.add(oid);
        return checkDistributionGroup(name, primaryAddress, members, ou, displayName, valueForFilter).getOid();
    }

    protected String createDistributionGroup(String name, String primaryAddress, Collection<String> members,
            String ou, String displayName, String valueForFilter) throws FaultMessage {

        Document doc = ModelClientUtil.getDocumnent();

        ShadowType shadow = new ShadowType();
        shadow.setName(ModelClientUtil.createPolyStringType(name, doc));
        shadow.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        shadow.setObjectClass(OC_DISTRIBUTION_GROUP);
        shadow.setKind(ShadowKindType.GENERIC);
        shadow.setIntent("distribution-group");

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "Type"), "Security", doc));
        attributes.getAny().add(
                ModelClientUtil.createTextElement(new QName(NS_RI, "PrimarySmtpAddress"), primaryAddress, doc));
        for (String member : members) {
            attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "Members"), member, doc));
        }
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "OrganizationalUnit"), ou, doc));
        attributes.getAny()
                .add(ModelClientUtil.createTextElement(new QName(NS_RI, "DisplayName"), displayName, doc));
        attributes.getAny().add(
                ModelClientUtil.createTextElement(new QName(NS_RI, "HiddenFromAddressListsEnabled"), "true", doc));
        attributes.getAny()
                .add(ModelClientUtil.createTextElement(new QName(NS_RI, "CustomAttribute1"), valueForFilter, doc));
        attributes.getAny().add(ModelClientUtil
                .createTextElement(new QName(NS_RI, "BypassSecurityGroupManagerCheck"), "true", doc));
        shadow.setAttributes(attributes);

        return createShadow(shadow);
    }

    protected ShadowType checkDistributionGroup(String name, String primaryAddress, Collection<String> members,
            String ou, String displayName, String valueForFilter) throws Exception {
        System.out.println("Retrieving DistributionGroup " + name);
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_DISTRIBUTION_GROUP, name);
        AssertJUnit.assertNotNull("DistributionGroup " + name + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "name", name);
        assertAttributeEquals(attrs, "RecipientType", "MailUniversalSecurityGroup");
        assertAttributeEquals(attrs, "PrimarySmtpAddress", primaryAddress);
        //Members cannot be retrieved in this way
        //assertAttributeEquals(attrs, "Members", members);
        assertAttributeEquals(attrs, "OrganizationalUnit", ou);
        assertAttributeEquals(attrs, "DisplayName", displayName);
        assertAttributeEquals(attrs, "HiddenFromAddressListsEnabled", "true");
        assertAttributeEquals(attrs, "CustomAttribute1", valueForFilter);
        return shadowType;
    }

    // =============== Users ===============

    protected void cleanup() throws Exception {
        deleteShadows(distributionGroups, true);
        deleteShadows(emailAddressPolicies, true);
        deleteShadows(addressBookPolicies, true);
        deleteShadows(offlineAddressBooks, true);
        deleteShadows(addressLists, true);
        deleteShadows(globalAddressLists, true);
        deleteShadows(acceptedDomains, true);
        deleteObjects(OrgType.class, orgs, true);
    }

    protected void deleteShadows(List<String> oids, boolean ignoreFailures) throws FaultMessage {
        deleteObjectsByOids(ShadowType.class, oids, ignoreFailures);
    }

    protected void deleteShadow(String oid, boolean ignoreFailures) throws FaultMessage {
        deleteObject(ShadowType.class, oid, ignoreFailures);
    }

    protected void deleteShadowRaw(String oid, boolean ignoreFailures) throws FaultMessage {
        deleteObject(ShadowType.class, oid, ignoreFailures, createRaw());
    }

    protected ModelExecuteOptionsType createRaw() {
        ModelExecuteOptionsType options = new ModelExecuteOptionsType();
        options.setRaw(true);
        return options;
    }

    protected void deleteObjectsByOids(Class objectClass, List<String> oids, boolean ignoreFailures)
            throws FaultMessage {
        for (String oid : oids) {
            deleteObject(objectClass, oid, ignoreFailures);
        }
    }

    protected void deleteObjects(Class objectClass, List<? extends ObjectType> objects, boolean ignoreFailures)
            throws FaultMessage {
        for (ObjectType objectType : objects) {
            deleteObject(objectClass, objectType.getOid(), ignoreFailures);
        }
    }

    protected void deleteObject(Class objectClass, String oid, boolean ignoreFailures) throws FaultMessage {
        deleteObject(objectClass, oid, ignoreFailures, new ModelExecuteOptionsType());
    }

    protected void deleteObject(Class objectClass, String oid, boolean ignoreFailures,
            ModelExecuteOptionsType executeOptionsType) throws FaultMessage {
        System.out.println("Deleting " + objectClass.getSimpleName() + " " + oid);
        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(objectClass));
        deltaType.setChangeType(ChangeTypeType.DELETE);
        deltaType.setOid(oid);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);

        if (!ignoreFailures) {
            modelPort.executeChanges(deltaListType, executeOptionsType);
        } else {
            try {
                modelPort.executeChanges(deltaListType, executeOptionsType);
            } catch (Exception e) {
                System.err.println("Cannot remove " + oid + ": " + e.getMessage());
            }
        }
    }

    protected void modifyShadow(String oid, String path, ModificationTypeType modType, Object value)
            throws Exception {
        modifyObject(ShadowType.class, oid, path, modType, value);
    }

    protected void modifyObject(Class objectType, String oid, String path, ModificationTypeType modType,
            Object value) throws Exception {
        modifyObject(objectType, oid, path, modType, value, null, true);
    }

    protected ObjectDeltaOperationType modifyObject(Class objectType, String oid, String path,
            ModificationTypeType modType, Object value, ModelExecuteOptionsType optionsType, boolean assertSuccess)
            throws Exception {
        System.out.println("Modifying " + objectType.getSimpleName() + " " + oid + " (path: " + path + ")");

        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(objectType));
        deltaType.setChangeType(ChangeTypeType.MODIFY);
        deltaType.setOid(oid);

        if (path != null) {
            ItemDeltaType itemDelta = new ItemDeltaType();
            itemDelta.setModificationType(modType);
            itemDelta.setPath(createNonDefaultItemPathType(path));
            if (!(value instanceof Collection)) {
                itemDelta.getValue().add(value);
            } else {
                itemDelta.getValue().addAll((Collection) value);
            }
            deltaType.getItemDelta().add(itemDelta);
        }

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType odolist = modelPort.executeChanges(deltaListType, optionsType);
        return assertExecuteChangesSuccess(odolist, deltaType, assertSuccess);
    }

    // values: path -> value or collection of values
    protected ObjectDeltaOperationType modifyObject(Class objectType, String oid, ModificationTypeType modType,
            Map<String, Object> values, ModelExecuteOptionsType optionsType, boolean assertSuccess)
            throws Exception {
        System.out.println("Modifying " + objectType.getSimpleName() + " " + oid + " (values: " + values + ")");

        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(objectType));
        deltaType.setChangeType(ChangeTypeType.MODIFY);
        deltaType.setOid(oid);

        for (Map.Entry<String, Object> entry : values.entrySet()) {
            ItemDeltaType itemDelta = new ItemDeltaType();
            itemDelta.setModificationType(modType);
            itemDelta.setPath(createNonDefaultItemPathType(entry.getKey()));
            if (!(entry.getValue() instanceof Collection)) {
                itemDelta.getValue().add(entry.getValue());
            } else {
                itemDelta.getValue().addAll((Collection) (entry.getValue()));
            }
            deltaType.getItemDelta().add(itemDelta);
        }

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType odolist = modelPort.executeChanges(deltaListType, optionsType);
        return assertExecuteChangesSuccess(odolist, deltaType, assertSuccess);
    }

    protected ObjectDeltaOperationType assertExecuteChangesSuccess(ObjectDeltaOperationListType odolist,
            ObjectDeltaType deltaType, boolean assertSuccess) {
        ObjectDeltaOperationType found = null;
        for (ObjectDeltaOperationType odo : odolist.getDeltaOperation()) {
            if (deltaType == null || deltaType.getOid().equals(odo.getObjectDelta().getOid())) {
                AssertJUnit.assertNotNull("Operation result is null", odo.getExecutionResult());
                found = odo;
                if (odo.getExecutionResult().getStatus() != OperationResultStatusType.SUCCESS) {
                    System.out.println("!!! Operation result is " + odo.getExecutionResult().getStatus());
                    System.out.println("!!! Message: " + odo.getExecutionResult().getMessage());
                    System.out.println("!!! Details:\n" + odo.getExecutionResult());
                    if (assertSuccess) {
                        AssertJUnit.assertEquals("Unexpected operation result status",
                                OperationResultStatusType.SUCCESS, odo.getExecutionResult().getStatus());
                    }
                }
            }
        }
        AssertJUnit.assertNotNull("ObjectDelta was not found in ObjectDeltaOperationList", found);
        return found;
    }

    protected void assertAttributeExists(Map<String, Object> attrs, String name) {
        AssertJUnit.assertTrue("Attribute " + name + " is missing", attrs.containsKey(name));
    }

    protected ShadowType checkAccount(String givenName, String sn, String dn, String container) throws Exception {
        System.out.println("Retrieving " + sn + " account...");
        ShadowType shadowType = getShadowByName(getResourceOid(), OC_ACCOUNT, dn);
        AssertJUnit.assertNotNull(sn + " was not found", shadowType);
        System.out.println("Done; shadow OID = " + shadowType.getOid());
        dumpAttributes(shadowType);
        Map<String, Object> attrs = getAttributesAsMap(shadowType);
        assertAttributeExists(attrs, "uid");
        assertAttributeEquals(attrs, "sAMAccountName", sn.toLowerCase());
        assertAttributeEquals(attrs, "distinguishedName", dn);
        assertAttributeEquals(attrs, "givenName", givenName);
        assertAttributeEquals(attrs, "sn", sn);
        assertAttributeEquals(attrs, "passwordExpired", true);
        assertAttributeEquals(attrs, "PasswordNeverExpires", "false");
        assertAttributeEquals(attrs, "ad_container", container);
        assertAttributeEquals(attrs, "objectClass",
                new HashSet(Arrays.asList("top", "person", "organizationalPerson", "user")));
        return shadowType;
    }

    protected void assertAttributeEquals(Map<String, Object> attrs, String name, Object expectedValue) {
        Object realValue = attrs.get(name);
        AssertJUnit.assertEquals("Unexpected value of attribute " + name, expectedValue, realValue);
    }

    protected void assertAttributeContains(Map<String, Object> attrs, String name, Object expectedValue) {
        if (expectedValue instanceof Collection) {
            for (Object singleValue : (Collection) expectedValue) {
                assertAttributeContains(attrs, name, singleValue);
            }
        } else {
            Object realValue = attrs.get(name);
            Collection realCollection;
            if (realValue == null) {
                realCollection = new ArrayList();
            } else if (!(realValue instanceof Collection)) {
                realCollection = Arrays.asList(realValue);
            } else {
                realCollection = (Collection) realValue;
            }
            if (!realCollection.contains(expectedValue)) {
                AssertJUnit.assertTrue("Attribute " + name + " was expected to contain " + expectedValue
                        + " but it doesn't: " + realCollection, false);
            }
        }
    }

    protected void dumpAttributes(ShadowType shadowType) {
        ShadowAttributesType attributes = shadowType.getAttributes();
        System.out.println("Attributes for " + shadowType.getObjectClass().getLocalPart() + " "
                + getOrig(shadowType.getName()) + " {" + attributes.getAny().size() + " entries):");
        for (Object item : attributes.getAny()) {
            if (item instanceof Element) {
                Element e = (Element) item;
                System.out.println(" - " + e.getLocalName() + ": " + e.getTextContent());
            } else if (item instanceof JAXBElement) {
                JAXBElement je = (JAXBElement) item;
                String typeInfo = je.getValue() instanceof String ? ""
                        : (" (" + je.getValue().getClass().getSimpleName() + ")");
                System.out.println(" - " + je.getName().getLocalPart() + ": " + je.getValue() + typeInfo);
            } else {
                System.out.println(" - " + item);
            }
        }
    }

    protected Map<String, Object> getAttributesAsMap(ShadowType shadowType) {
        Map<String, Object> rv = new HashMap<>();

        ShadowAttributesType attributes = shadowType.getAttributes();
        for (Object item : attributes.getAny()) {
            if (item instanceof Element) {
                Element e = (Element) item;
                put(rv, e.getLocalName(), e.getTextContent());
            } else if (item instanceof JAXBElement) {
                JAXBElement je = (JAXBElement) item;
                put(rv, je.getName().getLocalPart(), je.getValue());
            } else {
                // nothing to do here
            }
        }
        return rv;
    }

    protected void put(Map<String, Object> map, String name, Object value) {
        Object existing = map.get(name);
        if (existing == null) {
            map.put(name, value);
        } else if (!(existing instanceof Set)) {
            Set set = new HashSet();
            set.add(existing);
            set.add(value);
            map.put(name, set);
        } else {
            ((Set) existing).add(value);
        }
    }

    // TODO move to ModelClientUtil
    public static String getOrig(PolyStringType polyStringType) {
        if (polyStringType == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (Object o : polyStringType.getContent()) {
            if (o instanceof String) {
                sb.append(o);
            } else if (o instanceof Element) {
                Element e = (Element) o;
                if ("orig".equals(e.getLocalName())) {
                    return e.getTextContent();
                }
            } else if (o instanceof JAXBElement) {
                JAXBElement je = (JAXBElement) o;
                if ("orig".equals(je.getName().getLocalPart())) {
                    return (String) je.getValue();
                }
            }
        }
        return sb.toString();
    }

    public static void dump(Collection<? extends ObjectType> objects) {
        System.out.println("Objects returned: " + objects.size());
        for (ObjectType objectType : objects) {
            System.out.println(" - " + getOrig(objectType.getName()) + ": " + objectType);
        }
    }

    protected SystemConfigurationType getConfiguration() throws FaultMessage {

        Holder<ObjectType> objectHolder = new Holder<ObjectType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();

        modelPort.getObject(ModelClientUtil.getTypeQName(SystemConfigurationType.class),
                SystemObjectsType.SYSTEM_CONFIGURATION.value(), options, objectHolder, resultHolder);

        return (SystemConfigurationType) objectHolder.value;
    }

    protected Collection<ResourceType> listResources() throws SAXException, IOException, FaultMessage {
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        Holder<ObjectListType> objectListHolder = new Holder<ObjectListType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        modelPort.searchObjects(ModelClientUtil.getTypeQName(ResourceType.class), null, options, objectListHolder,
                resultHolder);

        ObjectListType objectList = objectListHolder.value;
        return (Collection) objectList.getObject();
    }

    protected ResourceType getResource(String oid) throws SAXException, IOException, FaultMessage {
        return getObject(ResourceType.class, oid);
    }

    protected RoleType getRole(String oid) throws SAXException, IOException, FaultMessage {
        return getObject(RoleType.class, oid);
    }

    protected <T extends ObjectType> T getObject(Class<T> clazz, String oid)
            throws SAXException, IOException, FaultMessage {
        return getObject(clazz, oid, new SelectorQualifiedGetOptionsType());
    }

    protected <T extends ObjectType> T getObjectNoFetch(Class<T> clazz, String oid)
            throws SAXException, IOException, FaultMessage {
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        SelectorQualifiedGetOptionType option = new SelectorQualifiedGetOptionType();
        GetOperationOptionsType getOptions = new GetOperationOptionsType();
        getOptions.setNoFetch(true);
        option.setOptions(getOptions);
        options.getOption().add(option);
        return getObject(clazz, oid, options);
    }

    protected <T extends ObjectType> T getObject(Class<T> clazz, String oid,
            SelectorQualifiedGetOptionsType options) throws SAXException, IOException, FaultMessage {
        Holder<ObjectType> objectHolder = new Holder<>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        modelPort.getObject(ModelClientUtil.getTypeQName(clazz), oid, options, objectHolder, resultHolder);

        return (T) objectHolder.value;
    }

    protected Collection<UserType> listUsers() throws SAXException, IOException, FaultMessage {
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        Holder<ObjectListType> objectListHolder = new Holder<ObjectListType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        // let's say we want to get first 3 users, sorted alphabetically by user name
        QueryType queryType = new QueryType(); // holds search query + paging options
        PagingType pagingType = new PagingType();
        pagingType.setMaxSize(3);
        pagingType.setOrderBy(ModelClientUtil.createItemPathType("name"));
        pagingType.setOrderDirection(OrderDirectionType.ASCENDING);
        queryType.setPaging(pagingType);

        modelPort.searchObjects(ModelClientUtil.getTypeQName(UserType.class), queryType, options, objectListHolder,
                resultHolder);

        ObjectListType objectList = objectListHolder.value;
        return (Collection) objectList.getObject();
    }

    protected Collection<TaskType> listTasks() throws SAXException, IOException, FaultMessage {
        SelectorQualifiedGetOptionsType operationOptions = new SelectorQualifiedGetOptionsType();

        // Let's say we want to retrieve tasks' next scheduled time (because this may be a costly operation if
        // JDBC based quartz scheduler is used, the fetching of this attribute has to be explicitly requested)
        SelectorQualifiedGetOptionType getNextScheduledTimeOption = new SelectorQualifiedGetOptionType();

        // prepare a selector (described by path) + options (saying to retrieve that attribute)
        ObjectSelectorType selector = new ObjectSelectorType();
        selector.setPath(ModelClientUtil.createItemPathType("nextRunStartTimestamp"));
        getNextScheduledTimeOption.setSelector(selector);
        GetOperationOptionsType selectorOptions = new GetOperationOptionsType();
        selectorOptions.setRetrieve(RetrieveOptionType.INCLUDE);
        getNextScheduledTimeOption.setOptions(selectorOptions);

        // add newly created option to the list of operation options
        operationOptions.getOption().add(getNextScheduledTimeOption);

        Holder<ObjectListType> objectListHolder = new Holder<ObjectListType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        modelPort.searchObjects(ModelClientUtil.getTypeQName(TaskType.class), null, operationOptions,
                objectListHolder, resultHolder);

        ObjectListType objectList = objectListHolder.value;
        return (Collection) objectList.getObject();
    }

    protected String createUserGuybrush(RoleType role) throws FaultMessage {
        Document doc = ModelClientUtil.getDocumnent();

        UserType user = new UserType();
        user.setName(ModelClientUtil.createPolyStringType("guybrush", doc));
        user.setFullName(ModelClientUtil.createPolyStringType("Guybrush Threepwood", doc));
        user.setGivenName(ModelClientUtil.createPolyStringType("Guybrush", doc));
        user.setFamilyName(ModelClientUtil.createPolyStringType("Threepwood", doc));
        user.setEmailAddress("guybrush@meleeisland.net");
        user.getOrganization().add(ModelClientUtil.createPolyStringType("Pirate Brethren International", doc));
        user.getOrganizationalUnit().add(ModelClientUtil.createPolyStringType("Pirate Wannabes", doc));
        user.setCredentials(ModelClientUtil.createPasswordCredentials("IwannaBEaPIRATE"));

        if (role != null) {
            // create user with a role assignment
            AssignmentType roleAssignment = createRoleAssignment(role.getOid());
            user.getAssignment().add(roleAssignment);
        }

        return createUser(user);
    }

    protected String createOrg(OrgType orgType) throws FaultMessage {
        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(OrgType.class));
        deltaType.setChangeType(ChangeTypeType.ADD);
        deltaType.setObjectToAdd(orgType);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType operationListType = modelPort.executeChanges(deltaListType, null);
        return ModelClientUtil.getOidFromDeltaOperationList(operationListType, deltaType);
    }

    protected String createAccount(String givenName, String sn, String name, String recipientType,
            String overrideMail, String samAccountName, String upn, Map<String, Object> additionalAttributes,
            boolean assertSuccess) throws FaultMessage {
        ShadowType user = prepareShadowType(givenName, sn, name, recipientType, overrideMail, samAccountName, upn,
                additionalAttributes);
        return createObject(ShadowType.class, user, null, assertSuccess);
    }

    protected String createAccount(String givenName, String sn, String name, String recipientType,
            String overrideMail, String samAccountName, String upn, boolean assertSuccess) throws FaultMessage {
        ShadowType user = prepareShadowType(givenName, sn, name, recipientType, overrideMail, samAccountName, upn,
                null);
        return createObject(ShadowType.class, user, null, assertSuccess);
    }

    protected String createAccount(String givenName, String sn, String name, String recipientType,
            String overrideMail) throws FaultMessage {
        ShadowType user = prepareShadowType(givenName, sn, name, recipientType, overrideMail, null, null, null);
        return createShadow(user);
    }

    protected ObjectDeltaOperationType createAccountOdo(String givenName, String sn, String name,
            String recipientType, String overrideMail) throws FaultMessage {
        ShadowType shadowType = prepareShadowType(givenName, sn, name, recipientType, overrideMail, null, null,
                null);

        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(ShadowType.class));
        deltaType.setChangeType(ChangeTypeType.ADD);
        deltaType.setObjectToAdd(shadowType);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType operationListType = modelPort.executeChanges(deltaListType, null);
        return ModelClientUtil.findInDeltaOperationList(operationListType, deltaType);
    }

    // additionalAttributes: e.g. "OfflineAddressBook" -> "OAB123"
    private ShadowType prepareShadowType(String givenName, String sn, String name, String recipientType,
            String overrideMail, String samAccountName, String upn, Map<String, Object> additionalAttributes) {
        if (samAccountName == null) {
            samAccountName = sn.toLowerCase();
        }
        Document doc = ModelClientUtil.getDocumnent();

        ShadowType user = new ShadowType();
        user.setName(ModelClientUtil.createPolyStringType(name, doc));
        user.setResourceRef(createObjectReferenceType(ResourceType.class, getResourceOid()));
        user.setObjectClass(OC_ACCOUNT);
        user.setKind(ShadowKindType.ACCOUNT);

        ShadowAttributesType attributes = new ShadowAttributesType();
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_ICFS, "name"), name, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "givenName"), givenName, doc));
        attributes.getAny().add(ModelClientUtil.createTextElement(new QName(NS_RI, "sn"), sn, doc));
        attributes.getAny()
                .add(ModelClientUtil.createTextElement(new QName(NS_RI, "RecipientType"), recipientType, doc));
        attributes.getAny()
                .add(ModelClientUtil.createTextElement(new QName(NS_RI, "sAMAccountName"), samAccountName, doc));
        if (overrideMail != null) {
            attributes.getAny().add(
                    ModelClientUtil.createTextElement(new QName(NS_RI, "EmailAddressPolicyEnabled"), "true", doc));
            attributes.getAny()
                    .add(ModelClientUtil.createTextElement(new QName(NS_RI, "EmailAddresses"), overrideMail, doc));
        }
        if (upn != null) {
            attributes.getAny()
                    .add(ModelClientUtil.createTextElement(new QName(NS_RI, "userPrincipalName"), upn, doc));
        }
        if (additionalAttributes != null) {
            for (Map.Entry<String, Object> entry : additionalAttributes.entrySet()) {
                if (entry.getValue() instanceof Collection) {
                    for (Object value : (Collection) entry.getValue()) {
                        addAttribute(attributes, entry.getKey(), value, doc);
                    }
                } else {
                    addAttribute(attributes, entry.getKey(), entry.getValue(), doc);
                }
            }
        }
        user.setAttributes(attributes);
        return user;
    }

    private void addAttribute(ShadowAttributesType attributes, String name, Object value, Document doc) {
        if (value != null) {
            attributes.getAny()
                    .add(ModelClientUtil.createTextElement(new QName(NS_RI, name), value.toString(), doc));
        }
    }

    protected ShadowType getShadowByName(String resourceOid, QName objectClass, String name)
            throws JAXBException, SAXException, IOException, FaultMessage {
        // WARNING: in a real case make sure that the username is properly escaped before putting it in XML
        SearchFilterType filter = ModelClientUtil.parseSearchFilterType(
                "                        <q:and xmlns:q='http://prism.evolveum.com/xml/ns/public/query-3' xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3'>\n"
                        + "                            <q:ref>\n"
                        + "                                <q:path>resourceRef</q:path>\n"
                        + "                                <q:value>\n"
                        + "                                    <oid>" + resourceOid + "</oid>\n"
                        + "                                    <type>ResourceType</type>\n"
                        + "                                </q:value>\n" + "                            </q:ref>\n"
                        + "                            <q:equal>\n"
                        + "                                <q:path>objectClass</q:path>\n"
                        + "                                <q:value xmlns:a=\"" + objectClass.getNamespaceURI()
                        + "\">a:" + objectClass.getLocalPart() + "</q:value>\n"
                        + "                            </q:equal>\n" + "                            <q:equal>\n"
                        + "                                <q:path>attributes/name</q:path>\n"
                        + "                                <q:value>" + name + "</q:value>\n"
                        + "                            </q:equal>\n" + "                        </q:and>\n");
        QueryType query = new QueryType();
        query.setFilter(filter);
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        Holder<ObjectListType> objectListHolder = new Holder<>();
        Holder<OperationResultType> resultHolder = new Holder<>();

        modelPort.searchObjects(ModelClientUtil.getTypeQName(ShadowType.class), query, options, objectListHolder,
                resultHolder);

        ObjectListType objectList = objectListHolder.value;
        List<ObjectType> objects = objectList.getObject();
        if (objects.isEmpty()) {
            return null;
        }
        if (objects.size() == 1) {
            return (ShadowType) objects.get(0);
        }
        throw new IllegalStateException("Expected to find a single shadow with name '" + name + "' but found "
                + objects.size() + " ones instead");
    }

    // TODO move to Util
    public static ObjectReferenceType createObjectReferenceType(Class<? extends ObjectType> typeClass, String oid) {
        ObjectReferenceType ort = new ObjectReferenceType();
        ort.setOid(oid);
        ort.setType(ModelClientUtil.getTypeQName(typeClass));
        return ort;
    }

    protected String createUserFromSystemResource(String resourcePath)
            throws FileNotFoundException, JAXBException, FaultMessage {
        UserType user = unmarshallResource(resourcePath);

        return createUser(user);
    }

    protected static <T> T unmarshallFile(File file) throws JAXBException, FileNotFoundException {
        JAXBContext jc = ModelClientUtil.instantiateJaxbContext();
        Unmarshaller unmarshaller = jc.createUnmarshaller();

        InputStream is = null;
        JAXBElement<T> element = null;
        try {
            is = new FileInputStream(file);
            element = (JAXBElement<T>) unmarshaller.unmarshal(is);
        } finally {
            if (is != null) {
                IOUtils.closeQuietly(is);
            }
        }
        if (element == null) {
            return null;
        }
        return element.getValue();
    }

    protected static <T> T unmarshallResource(String path) throws JAXBException, FileNotFoundException {
        JAXBContext jc = ModelClientUtil.instantiateJaxbContext();
        Unmarshaller unmarshaller = jc.createUnmarshaller();

        InputStream is = null;
        JAXBElement<T> element = null;
        try {
            is = AbstractTestForExchangeConnector.class.getClassLoader().getResourceAsStream(path);
            if (is == null) {
                throw new FileNotFoundException("System resource " + path + " was not found");
            }
            element = (JAXBElement<T>) unmarshaller.unmarshal(is);
        } finally {
            if (is != null) {
                IOUtils.closeQuietly(is);
            }
        }
        if (element == null) {
            return null;
        }
        return element.getValue();
    }

    protected String createUser(UserType userType) throws FaultMessage {
        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(UserType.class));
        deltaType.setChangeType(ChangeTypeType.ADD);
        deltaType.setObjectToAdd(userType);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType operationListType = modelPort.executeChanges(deltaListType, null);
        String oid = ModelClientUtil.getOidFromDeltaOperationList(operationListType, deltaType);
        return oid;
    }

    protected String createShadow(ShadowType shadowType) throws FaultMessage {
        return createShadow(shadowType, null);
    }

    protected String createShadow(ShadowType shadowType, ModelExecuteOptionsType options) throws FaultMessage {
        return createObject(ShadowType.class, shadowType, options);
    }

    protected String createObject(Class clazz, ObjectType objectType, ModelExecuteOptionsType options)
            throws FaultMessage {
        return createObject(clazz, objectType, options, true);
    }

    protected String createObject(Class clazz, ObjectType objectType, ModelExecuteOptionsType options,
            boolean assertSuccess) throws FaultMessage {

        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(clazz));
        deltaType.setChangeType(ChangeTypeType.ADD);
        deltaType.setObjectToAdd(objectType);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType operationListType = modelPort.executeChanges(deltaListType, options);
        lastOdo = assertExecuteChangesSuccess(operationListType, null, assertSuccess);
        //        Holder<OperationResultType> holder = new Holder<>();
        //        String oid = getOidFromDeltaOperationList(operationListType, deltaType, holder);
        //        AssertJUnit.assertNotNull("No operation result from create shadow operation", holder.value);
        //        if (holder.value != null && holder.value.getStatus() != OperationResultStatusType.SUCCESS) {
        //            System.out.println("!!! Operation result is " + holder.value.getStatus() + ": " + holder.value);
        //            AssertJUnit.assertEquals("Unexpected operation result status", OperationResultStatusType.SUCCESS, holder.value.getStatus());
        //        }
        return ModelClientUtil.getOidFromDeltaOperationList(operationListType, deltaType);
    }

    /**
     * Retrieves OID and OperationResult created by model Web Service from the returned list of ObjectDeltaOperations.
     *
     * @param operationListType result of the model web service executeChanges call
     * @param originalDelta original request used to find corresponding ObjectDeltaOperationType instance. Must be of ADD type.
     * @param operationResultHolder where the result will be put
     * @return OID if found
     *
     * PRELIMINARY IMPLEMENTATION. Currently the first returned ADD delta with the same object type as original delta is returned.
     *
     * TODO move to ModelClientUtil
     */
    public static String getOidFromDeltaOperationList(ObjectDeltaOperationListType operationListType,
            ObjectDeltaType originalDelta, Holder<OperationResultType> operationResultTypeHolder) {
        Validate.notNull(operationListType);
        Validate.notNull(originalDelta);
        if (originalDelta.getChangeType() != ChangeTypeType.ADD) {
            throw new IllegalArgumentException("Original delta is not of ADD type");
        }
        if (originalDelta.getObjectToAdd() == null) {
            throw new IllegalArgumentException("Original delta contains no object-to-be-added");
        }
        for (ObjectDeltaOperationType operationType : operationListType.getDeltaOperation()) {
            ObjectDeltaType objectDeltaType = operationType.getObjectDelta();
            if (objectDeltaType.getChangeType() == ChangeTypeType.ADD && objectDeltaType.getObjectToAdd() != null) {
                ObjectType objectAdded = (ObjectType) objectDeltaType.getObjectToAdd();
                if (objectAdded.getClass().equals(originalDelta.getObjectToAdd().getClass())) {
                    operationResultTypeHolder.value = operationType.getExecutionResult();
                    return objectAdded.getOid();
                }
            }
        }
        return null;
    }

    protected void changeUserPassword(String oid, String newPassword) throws FaultMessage {
        ItemDeltaType passwordDelta = new ItemDeltaType();
        passwordDelta.setModificationType(ModificationTypeType.REPLACE);
        passwordDelta.setPath(ModelClientUtil.createItemPathType("credentials/password/value"));
        passwordDelta.getValue().add(ModelClientUtil.createProtectedString(newPassword));

        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(UserType.class));
        deltaType.setChangeType(ChangeTypeType.MODIFY);
        deltaType.setOid(oid);
        deltaType.getItemDelta().add(passwordDelta);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        modelPort.executeChanges(deltaListType, null);
    }

    protected void changeUserGivenName(String oid, String newValue) throws FaultMessage {
        Document doc = ModelClientUtil.getDocumnent();

        ObjectDeltaType userDelta = new ObjectDeltaType();
        userDelta.setOid(oid);
        userDelta.setObjectType(ModelClientUtil.getTypeQName(UserType.class));
        userDelta.setChangeType(ChangeTypeType.MODIFY);

        ItemDeltaType itemDelta = new ItemDeltaType();
        itemDelta.setModificationType(ModificationTypeType.REPLACE);
        itemDelta.setPath(ModelClientUtil.createItemPathType("givenName"));
        itemDelta.getValue().add(ModelClientUtil.createPolyStringType(newValue, doc));
        userDelta.getItemDelta().add(itemDelta);
        ObjectDeltaListType deltaList = new ObjectDeltaListType();
        deltaList.getDelta().add(userDelta);
        modelPort.executeChanges(deltaList, null);
    }

    protected boolean assignRoles(Class clazz, String oid, String... roleOids) throws FaultMessage {
        return modifyRoleAssignment(clazz, oid, true, roleOids);
    }

    protected void unassignRoles(Class clazz, String oid, String... roleOids) throws FaultMessage {
        modifyRoleAssignment(clazz, oid, false, roleOids);
    }

    protected boolean modifyRoleAssignment(Class clazz, String oid, boolean isAdd, String... roleOids)
            throws FaultMessage {
        ItemDeltaType assignmentDelta = new ItemDeltaType();
        if (isAdd) {
            assignmentDelta.setModificationType(ModificationTypeType.ADD);
        } else {
            assignmentDelta.setModificationType(ModificationTypeType.DELETE);
        }
        assignmentDelta.setPath(ModelClientUtil.createItemPathType("assignment"));
        for (String roleOid : roleOids) {
            AssertJUnit.assertNotNull("role OID is null", roleOid);
            assignmentDelta.getValue().add(createRoleAssignment(roleOid));
        }

        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(clazz));
        deltaType.setChangeType(ChangeTypeType.MODIFY);
        deltaType.setOid(oid);
        deltaType.getItemDelta().add(assignmentDelta);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);
        ObjectDeltaOperationListType objectDeltaOperationList = modelPort.executeChanges(deltaListType, null);
        Boolean success = null;
        for (ObjectDeltaOperationType objectDeltaOperation : objectDeltaOperationList.getDeltaOperation()) {
            if (oid.equals(objectDeltaOperation.getObjectDelta().getOid())) {
                if (!OperationResultStatusType.SUCCESS
                        .equals(objectDeltaOperation.getExecutionResult().getStatus())) {
                    System.out.println(
                            "*** Operation result = " + objectDeltaOperation.getExecutionResult().getStatus() + ": "
                                    + objectDeltaOperation.getExecutionResult().getMessage());
                    success = false;
                } else {
                    if (success == null) {
                        success = true;
                    }
                }
            }
        }
        return Boolean.TRUE.equals(success);
    }

    protected AssignmentType createRoleAssignment(String roleOid) {
        AssignmentType roleAssignment = new AssignmentType();
        ObjectReferenceType roleRef = new ObjectReferenceType();
        roleRef.setOid(roleOid);
        roleRef.setType(ModelClientUtil.getTypeQName(RoleType.class));
        roleAssignment.setTargetRef(roleRef);
        return roleAssignment;
    }

    protected UserType searchUserByName(String username)
            throws SAXException, IOException, FaultMessage, JAXBException {
        // WARNING: in a real case make sure that the username is properly escaped before putting it in XML
        SearchFilterType filter = ModelClientUtil.parseSearchFilterType(
                "<equal xmlns='http://prism.evolveum.com/xml/ns/public/query-3' xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3' >"
                        + "<path>c:name</path>" + "<value>" + username + "</value>" + "</equal>");
        QueryType query = new QueryType();
        query.setFilter(filter);
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        Holder<ObjectListType> objectListHolder = new Holder<ObjectListType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        modelPort.searchObjects(ModelClientUtil.getTypeQName(UserType.class), query, options, objectListHolder,
                resultHolder);

        ObjectListType objectList = objectListHolder.value;
        List<ObjectType> objects = objectList.getObject();
        if (objects.isEmpty()) {
            return null;
        }
        if (objects.size() == 1) {
            return (UserType) objects.get(0);
        }
        throw new IllegalStateException("Expected to find a single user with username '" + username + "' but found "
                + objects.size() + " users instead");
    }

    protected RoleType searchRoleByName(String roleName)
            throws SAXException, IOException, FaultMessage, JAXBException {
        // WARNING: in a real case make sure that the role name is properly escaped before putting it in XML
        SearchFilterType filter = ModelClientUtil.parseSearchFilterType(
                "<equal xmlns='http://prism.evolveum.com/xml/ns/public/query-3' xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3' >"
                        + "<path>c:name</path>" + "<value>" + roleName + "</value>" + "</equal>");
        QueryType query = new QueryType();
        query.setFilter(filter);
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        Holder<ObjectListType> objectListHolder = new Holder<ObjectListType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        modelPort.searchObjects(ModelClientUtil.getTypeQName(RoleType.class), query, options, objectListHolder,
                resultHolder);

        ObjectListType objectList = objectListHolder.value;
        List<ObjectType> objects = objectList.getObject();
        if (objects.isEmpty()) {
            return null;
        }
        if (objects.size() == 1) {
            return (RoleType) objects.get(0);
        }
        throw new IllegalStateException("Expected to find a single role with name '" + roleName + "' but found "
                + objects.size() + " users instead");
    }

    protected Collection<RoleType> listRequestableRoles()
            throws SAXException, IOException, FaultMessage, JAXBException {
        SearchFilterType filter = ModelClientUtil.parseSearchFilterType(
                "<equal xmlns='http://prism.evolveum.com/xml/ns/public/query-3' xmlns:c='http://midpoint.evolveum.com/xml/ns/public/common/common-3' >"
                        + "<path>c:requestable</path>" + "<value>true</value>" + "</equal>");
        QueryType query = new QueryType();
        query.setFilter(filter);
        SelectorQualifiedGetOptionsType options = new SelectorQualifiedGetOptionsType();
        Holder<ObjectListType> objectListHolder = new Holder<ObjectListType>();
        Holder<OperationResultType> resultHolder = new Holder<OperationResultType>();

        modelPort.searchObjects(ModelClientUtil.getTypeQName(RoleType.class), query, options, objectListHolder,
                resultHolder);

        ObjectListType objectList = objectListHolder.value;
        return (Collection) objectList.getObject();
    }

    protected void deleteUser(String oid) throws FaultMessage {
        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(UserType.class));
        deltaType.setChangeType(ChangeTypeType.DELETE);
        deltaType.setOid(oid);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);

        ModelExecuteOptionsType executeOptionsType = new ModelExecuteOptionsType();
        executeOptionsType.setRaw(true);
        modelPort.executeChanges(deltaListType, executeOptionsType);
    }

    protected void deleteTask(String oid) throws FaultMessage {
        ObjectDeltaType deltaType = new ObjectDeltaType();
        deltaType.setObjectType(ModelClientUtil.getTypeQName(TaskType.class));
        deltaType.setChangeType(ChangeTypeType.DELETE);
        deltaType.setOid(oid);

        ObjectDeltaListType deltaListType = new ObjectDeltaListType();
        deltaListType.getDelta().add(deltaType);

        ModelExecuteOptionsType executeOptionsType = new ModelExecuteOptionsType();
        executeOptionsType.setRaw(true);
        modelPort.executeChanges(deltaListType, executeOptionsType);
    }

    public ModelPortType createModelPort(String[] args) {
        String endpointUrl = DEFAULT_ENDPOINT_URL;

        if (args.length > 0) {
            endpointUrl = args[0];
        }

        System.out.println("Endpoint URL: " + endpointUrl);

        // uncomment this if you want to use Fiddler or any other proxy
        //        ProxySelector.setDefault(new MyProxySelector("127.0.0.1", 8888));

        ModelService modelService = new ModelService();
        ModelPortType modelPort = modelService.getModelPort();
        BindingProvider bp = (BindingProvider) modelPort;
        Map<String, Object> requestContext = bp.getRequestContext();
        requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointUrl);

        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(modelPort);

        HTTPConduit http = (HTTPConduit) client.getConduit();
        HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
        httpClientPolicy.setReceiveTimeout(300000L);
        http.setClient(httpClientPolicy);

        org.apache.cxf.endpoint.Endpoint cxfEndpoint = client.getEndpoint();

        Map<String, Object> outProps = new HashMap<String, Object>();

        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        outProps.put(WSHandlerConstants.USER, ADM_USERNAME);
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
        outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordHandler.class.getName());

        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
        cxfEndpoint.getOutInterceptors().add(wssOut);
        // enable the following to get client-side logging of outgoing requests and incoming responses
        //cxfEndpoint.getOutInterceptors().add(new LoggingOutInterceptor());
        //cxfEndpoint.getInInterceptors().add(new LoggingInInterceptor());

        return modelPort;
    }

    public static ItemPathType createNonDefaultItemPathType(String stringPath) {
        ItemPathType itemPathType = new ItemPathType();
        itemPathType.setValue(stringPath);
        return itemPathType;
    }

    protected String getDebugName(ObjectType objectType) {
        return objectType.getClass().getSimpleName() + " " + getOrig(objectType.getName()) + " ("
                + objectType.getOid() + ")";
    }

}