org.rhq.test.arquillian.FakeServerInventory.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.test.arquillian.FakeServerInventory.java

Source

/*
 * RHQ Management Platform
 * Copyright (C) 2005-2010 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.rhq.test.arquillian;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import org.rhq.core.clientapi.agent.upgrade.ResourceUpgradeRequest;
import org.rhq.core.clientapi.agent.upgrade.ResourceUpgradeResponse;
import org.rhq.core.clientapi.server.discovery.InventoryReport;
import org.rhq.core.domain.discovery.ResourceSyncInfo;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceError;
import org.rhq.core.domain.resource.ResourceErrorType;
import org.rhq.core.domain.resource.ResourceType;

/**
 * This class represents a server side database store of the inventory for the purposes
 * of the unit tests that need to mock out the server functionality.
 * <p>
 * The methods are not exhaustive and were added on as-needed basis. If this class doesn't
 * cover what you need, either add the functionality directly to it, subclass or roll your
 * own. It is only meant as a helper.
 * <p>
 * This impl uses mockito for defining the answers to various calls.
 * 
 * @author Lukas Krejci
 */
// TODO (ips): Why don't we just make this implement DiscoveryServerService and use it as the actual server service?
public class FakeServerInventory {

    private static final Log LOG = LogFactory.getLog(FakeServerInventory.class);

    /**
     * You can {@link #waitForDiscoveryComplete()} on an instance of this class
     * for the complete discovery to finish in case your
     * fake server commits some resources (which starts off
     * asynchronous discovery of children).
     * 
     *
     * @author Lukas Krejci
     */
    public static class CompleteDiscoveryChecker {
        private boolean depthReached;
        private final int expectedDepth;
        private final Object sync = new Object();

        public CompleteDiscoveryChecker(int expectedDepth) {
            this.expectedDepth = expectedDepth;
        }

        public void waitForDiscoveryComplete() throws InterruptedException {
            waitForDiscoveryComplete(0);
        }

        public void waitForDiscoveryComplete(long timeoutMillis) throws InterruptedException {
            synchronized (sync) {
                if (!depthReached) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Waiting for the discovery to complete on " + this);
                    }
                    sync.wait(timeoutMillis);
                } else {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Discovery already complete... no need to wait on " + this);
                    }
                }
            }
        }

        private void setDepth(int resourceTreeDepth) {
            synchronized (sync) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Current tree depth is " + resourceTreeDepth + ", while this checker is waiting for "
                            + expectedDepth + " on " + this);
                }
                if (!depthReached && resourceTreeDepth >= expectedDepth) {
                    depthReached = true;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                //just way some more to give the PC time to really finish
                                //the handling of the inventory report.
                                //I know this sucks and is prone to races but we cannot
                                //properly implement this without modifying the plugin container.
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("Ad-hoc wait for discovery to really complete...");
                                }
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                //well, we are going to finish in a few anyway
                            } finally {
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("Notifying about discovery complete on "
                                            + CompleteDiscoveryChecker.this);
                                }
                                synchronized (sync) {
                                    sync.notifyAll();
                                }
                            }
                        }
                    }).start();
                }
            }
        }
    }

    private Resource platform;
    private Map<String, Resource> resourceStore = new HashMap<String, Resource>();
    private int counter;
    private boolean failing;
    private boolean failUpgrade;

    private CompleteDiscoveryChecker discoveryChecker;

    private static final Comparator<Resource> ID_COMPARATOR = new Comparator<Resource>() {
        @Override
        public int compare(Resource o1, Resource o2) {
            return o1.getId() - o2.getId();
        }
    };

    private static final Comparator<Resource> RESOURCE_TYPE_COMPARATOR = new Comparator<Resource>() {
        @Override
        public int compare(Resource o1, Resource o2) {
            return o1.getResourceType().equals(o2.getResourceType()) ? 0 : o1.getId() - o2.getId();
        }
    };

    public FakeServerInventory() {
        this(false);
    }

    public FakeServerInventory(boolean failing) {
        this.failing = failing;
    }

    public synchronized void prepopulateInventory(Resource platform, Collection<Resource> topLevelServers) {
        this.platform = fakePersist(platform, InventoryStatus.COMMITTED, new HashSet<String>());
        for (Resource res : topLevelServers) {
            res.setParentResource(this.platform);
            fakePersist(res, InventoryStatus.COMMITTED, new HashSet<String>());
        }
    }

    public CompleteDiscoveryChecker createAsyncDiscoveryCompletionChecker(int expectedResourceTreeDepth) {
        discoveryChecker = new CompleteDiscoveryChecker(expectedResourceTreeDepth);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Created a new discovery complete checker with tree depth " + expectedResourceTreeDepth + ": "
                    + discoveryChecker);
        }
        return discoveryChecker;
    }

    public synchronized Answer<ResourceSyncInfo> mergeInventoryReport(
            final InventoryStatus requiredInventoryStatus) {
        return new Answer<ResourceSyncInfo>() {
            @Override
            public ResourceSyncInfo answer(InvocationOnMock invocation) throws Throwable {
                synchronized (FakeServerInventory.this) {
                    InventoryReport inventoryReport = (InventoryReport) invocation.getArguments()[0];

                    try {
                        throwIfFailing();

                        for (Resource res : inventoryReport.getAddedRoots()) {
                            Resource persisted = fakePersist(res, requiredInventoryStatus, new HashSet<String>());

                            if (res.getParentResource() == Resource.ROOT) {
                                platform = persisted;
                            }
                        }
                        return getSyncInfo();
                    } finally {
                        if (discoveryChecker != null && !inventoryReport.getAddedRoots().isEmpty()) {
                            discoveryChecker.setDepth(getResourceTreeDepth());
                        }
                    }
                }
            }
        };
    }

    public synchronized int getResourceTreeDepth() {
        if (platform == null) {
            return 0;
        }

        return getTreeDepth(platform);
    }

    private static int getTreeDepth(Resource root) {
        int maxDepth = 0;
        for (Resource c : root.getChildResources()) {
            int childDepth = getTreeDepth(c);
            if (maxDepth < childDepth) {
                maxDepth = childDepth;
            }
        }

        return maxDepth + 1;
    }

    public synchronized Answer<ResourceSyncInfo> clearPlatform() {
        return new Answer<ResourceSyncInfo>() {
            @Override
            public ResourceSyncInfo answer(InvocationOnMock invocation) throws Throwable {
                synchronized (FakeServerInventory.this) {
                    throwIfFailing();

                    platform = null;

                    return getSyncInfo();
                }
            }
        };
    }

    public synchronized Answer<Void> setResourceError() {
        return new Answer<Void>() {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                synchronized (FakeServerInventory.this) {
                    throwIfFailing();

                    ResourceError error = (ResourceError) invocation.getArguments()[0];

                    Resource serverSideResource = resourceStore.get(error.getResource().getUuid());

                    if (serverSideResource != null) {
                        List<ResourceError> currentErrors = serverSideResource.getResourceErrors();
                        currentErrors.add(error);
                    }

                    return null;
                }
            }
        };
    }

    public synchronized Answer<Set<ResourceUpgradeResponse>> upgradeResources() {
        return new Answer<Set<ResourceUpgradeResponse>>() {
            @Override
            @SuppressWarnings({ "serial", "unchecked" })
            public Set<ResourceUpgradeResponse> answer(InvocationOnMock invocation) throws Throwable {
                synchronized (FakeServerInventory.this) {
                    throwIfFailing();

                    if (failUpgrade) {
                        throw new RuntimeException("Failing the upgrade purposefully.");
                    }

                    Object[] args = invocation.getArguments();
                    Set<ResourceUpgradeRequest> requests = (Set<ResourceUpgradeRequest>) args[0];
                    Set<ResourceUpgradeResponse> responses = new HashSet<ResourceUpgradeResponse>();

                    for (final ResourceUpgradeRequest request : requests) {
                        Resource resource = findResource(platform, new Resource() {
                            @Override
                            public int getId() {
                                return request.getResourceId();
                            }
                        }, ID_COMPARATOR);
                        if (resource != null) {
                            if (request.getNewDescription() != null) {
                                resource.setDescription(request.getNewDescription());
                            }
                            if (request.getNewName() != null) {
                                resource.setName(request.getNewName());
                            }

                            if (request.getNewResourceKey() != null) {
                                resource.setResourceKey(request.getNewResourceKey());
                            }

                            if (request.getUpgradeErrorMessage() != null) {
                                ResourceError error = new ResourceError(resource, ResourceErrorType.UPGRADE,
                                        request.getUpgradeErrorMessage(), request.getUpgradeErrorStackTrace(),
                                        request.getTimestamp());
                                resource.getResourceErrors().add(error);
                            }

                            ResourceUpgradeResponse resp = new ResourceUpgradeResponse();
                            resp.setResourceId(resource.getId());
                            resp.setUpgradedResourceName(resource.getName());
                            resp.setUpgradedResourceKey(resource.getResourceKey());
                            resp.setUpgradedResourceDescription(resource.getDescription());
                            responses.add(resp);
                        }
                    }
                    return responses;
                }
            }
        };
    }

    public synchronized Answer<Set<Resource>> getResources() {
        return new Answer<Set<Resource>>() {
            @Override
            @SuppressWarnings("unchecked")
            public Set<Resource> answer(InvocationOnMock invocation) throws Throwable {
                synchronized (FakeServerInventory.this) {
                    throwIfFailing();

                    Object[] args = invocation.getArguments();
                    Set<Integer> resourceIds = (Set<Integer>) args[0];
                    boolean includeDescendants = (Boolean) args[1];

                    return getResources(resourceIds, includeDescendants);
                }
            }
        };
    }

    public synchronized boolean isFailing() {
        return failing;
    }

    public synchronized void setFailing(boolean failing) {
        this.failing = failing;
    }

    public synchronized boolean isFailUpgrade() {
        return failUpgrade;
    }

    public synchronized void setFailUpgrade(boolean failUpgrade) {
        this.failUpgrade = failUpgrade;
    }

    @SuppressWarnings("serial")
    public synchronized Set<Resource> findResourcesByType(final ResourceType type) {
        Set<Resource> result = new HashSet<Resource>();
        if (platform != null) {
            findResources(platform, new Resource() {
                @Override
                public ResourceType getResourceType() {
                    return type;
                }
            }, result, RESOURCE_TYPE_COMPARATOR);
        }
        return result;
    }

    @SuppressWarnings("serial")
    private Set<Resource> getResources(Set<Integer> resourceIds, boolean includeDescendants) {
        //it is important to keep the hierarchical order of the resource in the returned set
        //so that plugin container can merge the resources from top to bottom.
        Set<Resource> result = new LinkedHashSet<Resource>();

        for (final Integer id : resourceIds) {
            Resource r = findResource(platform, new Resource() {
                @Override
                public int getId() {
                    return id;
                }
            }, ID_COMPARATOR);
            if (r != null) {
                result.add(r);

                if (includeDescendants) {
                    for (Resource child : r.getChildResources()) {
                        result.addAll(getResources(Collections.singleton(child.getId()), true));
                    }
                }
            }
        }

        return result;
    }

    public void removeResource(Resource r) {
        resourceStore.remove(r.getUuid());
        Resource parent = r.getParentResource();
        if (parent != null) {
            parent.getChildResources().remove(r);
        }
        for (Resource child : r.getChildResources()) {
            removeResource(child);
        }
    }

    public Map<String, Resource> getResourceStore() {
        return resourceStore;
    }

    private Resource fakePersist(Resource agentSideResource, InventoryStatus requiredInventoryStatus,
            Set<String> inProgressUUIds) {
        Resource persisted = resourceStore.get(agentSideResource.getUuid());
        if (!inProgressUUIds.add(agentSideResource.getUuid())) {
            return persisted;
        }
        if (persisted == null) {
            persisted = new Resource();
            if (agentSideResource.getId() != 0 && counter < agentSideResource.getId()) {
                counter = agentSideResource.getId() - 1;
            }
            persisted.setId(++counter);
            persisted.setUuid(agentSideResource.getUuid());
            persisted.setAgent(agentSideResource.getAgent());
            persisted.setCurrentAvailability(agentSideResource.getCurrentAvailability());
            persisted.setDescription(agentSideResource.getDescription());
            persisted.setName(agentSideResource.getName());
            persisted.setPluginConfiguration(agentSideResource.getPluginConfiguration().clone());
            persisted.setResourceConfiguration(agentSideResource.getResourceConfiguration().clone());
            persisted.setVersion(agentSideResource.getVersion());
            persisted.setInventoryStatus(requiredInventoryStatus);
            persisted.setResourceKey(agentSideResource.getResourceKey());
            persisted.setResourceType(agentSideResource.getResourceType());
            resourceStore.put(persisted.getUuid(), persisted);
        }

        Resource parent = agentSideResource.getParentResource();
        if (parent != null && parent != Resource.ROOT) {
            parent = fakePersist(agentSideResource.getParentResource(), requiredInventoryStatus, inProgressUUIds);
            persisted.setParentResource(parent);
            parent.getChildResources().add(persisted);
        } else {
            persisted.setParentResource(parent);
        }

        //persist the children
        Set<Resource> childResources = new LinkedHashSet<Resource>();
        for (Resource child : agentSideResource.getChildResources()) {
            childResources.add(fakePersist(child, requiredInventoryStatus, inProgressUUIds));
        }
        //now update the list with whatever the persisted resource contained in the past
        //i.e. we prefer the current results from the agent but keep the children we used to
        //have in the past. This is the same behavior as the actual RHQ server has.
        childResources.addAll(persisted.getChildResources());

        persisted.setChildResources(childResources);

        inProgressUUIds.remove(agentSideResource.getUuid());

        return persisted;
    }

    private ResourceSyncInfo getSyncInfo() {
        return platform != null ? convert(platform) : null;
    }

    private void throwIfFailing() {
        if (failing) {
            throw new RuntimeException("Fake server inventory is in the failing mode.");
        }
    }

    private static ResourceSyncInfo convert(Resource root) {
        return convertInternal(root, new HashMap<String, ResourceSyncInfo>());
    }

    private static ResourceSyncInfo convertInternal(Resource root,
            Map<String, ResourceSyncInfo> intermediateResults) {
        ResourceSyncInfo ret = intermediateResults.get(root.getUuid());

        if (ret != null) {
            return ret;
        }

        try {
            ret = new ResourceSyncInfo();

            intermediateResults.put(root.getUuid(), ret);

            Class<ResourceSyncInfo> clazz = ResourceSyncInfo.class;

            getPrivateField(clazz, "id").set(ret, root.getId());
            getPrivateField(clazz, "uuid").set(ret, root.getUuid());
            getPrivateField(clazz, "mtime").set(ret, root.getMtime());
            getPrivateField(clazz, "inventoryStatus").set(ret, root.getInventoryStatus());

            ResourceSyncInfo parent = root.getParentResource() == null ? null
                    : convertInternal(root.getParentResource(), intermediateResults);

            getPrivateField(clazz, "parent").set(ret, parent);

            Set<ResourceSyncInfo> children = new LinkedHashSet<ResourceSyncInfo>();
            for (Resource child : root.getChildResources()) {
                ResourceSyncInfo syncChild = convertInternal(child, intermediateResults);

                children.add(syncChild);
            }
            getPrivateField(clazz, "childSyncInfos").set(ret, children);

            return ret;
        } catch (Exception e) {
            throw new IllegalStateException(
                    "Failed to convert resource " + root + " to a ResourceSyncInfo. This should not happen.", e);
        }
    }

    private static Field getPrivateField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        Field field = clazz.getDeclaredField(fieldName);
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }

        return field;
    }

    private static Resource findResource(Resource root, Resource template, Comparator<Resource> comparator) {
        if (root == null)
            return null;
        if (comparator.compare(root, template) == 0) {
            return root;
        } else {
            for (Resource child : root.getChildResources()) {
                Resource found = findResource(child, template, comparator);
                if (found != null)
                    return found;
            }
        }

        return null;
    }

    private static void findResources(Resource root, Resource template, Set<Resource> result,
            Comparator<Resource> comparator) {
        if (root == null)
            return;
        if (comparator.compare(root, template) == 0) {
            result.add(root);
        } else {
            for (Resource child : root.getChildResources()) {
                findResources(child, template, result, comparator);
            }
        }
    }
}