com.xebialabs.deployit.ci.server.DeployitDescriptorRegistryImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.xebialabs.deployit.ci.server.DeployitDescriptorRegistryImpl.java

Source

/**
 * Copyright (c) 2014, XebiaLabs B.V., All rights reserved.
 *
 *
 * The XL Deploy plugin for Jenkins is licensed under the terms of the GPLv2
 * <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most XebiaLabs Libraries.
 * There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
 * this software, see the FLOSS License Exception
 * <https://github.com/jenkinsci/deployit-plugin/blob/master/LICENSE>.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation; version 2
 * of the License.
 *
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
 * Floor, Boston, MA 02110-1301  USA
 */

package com.xebialabs.deployit.ci.server;

import java.util.Collection;
import java.util.List;
import java.util.UUID;

import javax.annotation.Nullable;

import com.xebialabs.deployit.ci.ArtifactView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Ordering;
import com.google.common.util.concurrent.Monitor;

import com.xebialabs.deployit.booter.remote.BooterConfig;
import com.xebialabs.deployit.booter.remote.DeployitCommunicator;
import com.xebialabs.deployit.booter.remote.RemoteBooter;
import com.xebialabs.deployit.booter.remote.RemoteDescriptor;
import com.xebialabs.deployit.booter.remote.RemoteDescriptorRegistry;
import com.xebialabs.deployit.booter.remote.RemotePropertyDescriptor;
import com.xebialabs.deployit.ci.DeployitPluginException;
import com.xebialabs.deployit.plugin.api.reflect.Descriptor;
import com.xebialabs.deployit.plugin.api.reflect.DescriptorRegistry;
import com.xebialabs.deployit.plugin.api.reflect.PropertyDescriptor;
import com.xebialabs.deployit.plugin.api.reflect.PropertyKind;
import com.xebialabs.deployit.plugin.api.reflect.Type;
import com.xebialabs.deployit.plugin.api.udm.ConfigurationItem;
import com.xebialabs.deployit.plugin.api.udm.Deployable;
import com.xebialabs.deployit.plugin.api.udm.DeploymentPackage;
import com.xebialabs.deployit.plugin.api.udm.EmbeddedDeployable;
import com.xebialabs.deployit.plugin.api.udm.Version;
import com.xebialabs.deployit.plugin.api.udm.artifact.FolderArtifact;
import com.xebialabs.deployit.plugin.api.udm.artifact.SourceArtifact;
import com.xebialabs.deployit.plugin.api.udm.base.BaseConfigurationItem;
import com.xebialabs.deployit.plugin.api.udm.base.BaseDeployable;
import com.xebialabs.deployit.plugin.api.udm.base.BaseDeployableFileArtifact;
import com.xebialabs.deployit.plugin.api.udm.base.BaseDeployableFolderArtifact;
import com.xebialabs.deployit.plugin.api.udm.base.BaseEmbeddedDeployable;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.common.collect.Sets.newLinkedHashSet;

public class DeployitDescriptorRegistryImpl implements DeployitDescriptorRegistry {
    private static final Logger LOG = LoggerFactory.getLogger(DeployitDescriptorRegistryImpl.class);
    private BooterConfig booterConfig;

    private Monitor LOCK = new Monitor();
    private Iterable<Descriptor> allDeployableDescriptors;

    private DeployitCommunicator communicator;
    private String version;

    DeployitDescriptorRegistryImpl(BooterConfig booterConfig) {
        this.booterConfig = booterConfig;
        this.version = UUID.randomUUID().toString();
    }

    @Override
    public DeployitCommunicator getCommunicator() {
        LOCK.enter();
        try {
            if (null == communicator) {
                try {
                    communicator = RemoteBooter.getCommunicator(booterConfig);
                    LOG.debug("Reusing existing communicator for config: {}.", safeBooterConfigKey());
                } catch (IllegalStateException ex) {
                    LOG.debug("No communicator found for config: {}. Creating new DeployitCommunicator.",
                            safeBooterConfigKey());
                    DescriptorRegistry.remove(booterConfig);
                    communicator = RemoteBooter.boot(booterConfig);
                }

                fixVersionDepl6949();
            }
        } finally {
            LOCK.leave();
        }

        return communicator;
    }

    private String safeBooterConfigKey() {
        String safePassword = booterConfig.getPassword().replaceAll(".", "*");
        return BooterConfig.builder().withProtocol(booterConfig.getProtocol())
                .withCredentials(booterConfig.getUsername(), safePassword).withHost(booterConfig.getHost())
                .withPort(booterConfig.getPort()).withContext(booterConfig.getContext())
                .withProxyHost(booterConfig.getProxyHost()).withProxyPort(booterConfig.getProxyPort()).build()
                .getKey();
    }

    private RemoteDescriptorRegistry getDescriptorRegistry() {
        if (DescriptorRegistry.getDescriptorRegistry(booterConfig) == null) {
            getCommunicator();
        }
        return (RemoteDescriptorRegistry) DescriptorRegistry.getDescriptorRegistry(booterConfig);
    }

    @Override
    public Type typeForClass(Class<?> clazz) {
        return getDescriptorRegistry().lookupType(clazz);
    }

    @Override
    public Type typeForName(String name) {
        return getDescriptorRegistry().lookupType(name);
    }

    /**
     * This method is only used when we create Dar on a remote side (ie jenkins plugin).
     * TODO: Once the DarPackager and ManifestWriter are rewritten and start to accept generic CIs (DEPL-6925)
     * this method should be removed.
     */
    @Override
    public <T extends BaseConfigurationItem> T newInstance(Class<T> clazz, String id) {
        Type type = typeForClass(clazz);
        try {
            T bci = clazz.newInstance();
            bci.setId(id);
            bci.setType(type);
            initSyntheticProperties(bci, type);

            return bci;
        } catch (Throwable t) {
            String msg = String.format("Unable to create CI class '%s' with id: '%s'", clazz.toString(), id);
            throw new DeployitPluginException(msg, t);
        }
    }

    private <T extends BaseConfigurationItem> void initSyntheticProperties(T bci, Type type) {
        prefillPropertiesWithDefaultValues(bci, type);
    }

    private <T extends BaseConfigurationItem> void prefillPropertiesWithDefaultValues(T bci, Type type) {
        Collection<PropertyDescriptor> propertyDescriptors = type.getDescriptor().getPropertyDescriptors();
        // properties with default values
        for (PropertyDescriptor pd : propertyDescriptors) {
            Object defaultValue = pd.getDefaultValue();
            // hack for boolean
            if (defaultValue == null && PropertyKind.BOOLEAN == pd.getKind()) {
                defaultValue = false;
            }
            if (defaultValue != null) {
                bci.setProperty(pd.getName(), defaultValue);
            }
        }
    }

    // DEPL-6949: add properties required by the DarPackager/ManifestWriter even if they don't exist on XLD
    private void fixVersionDepl6949() {
        // monkey patching "udm.Version" on client side
        Type type = typeForClass(DeploymentPackage.class);
        if (type.isSubTypeOf(Type.valueOf(Version.class))) {
            addMissingPropertyDescriptor(type, "ignoreUndefinedPropertiesInManifest", PropertyKind.BOOLEAN, "true",
                    true);
            addMissingPropertyDescriptor(type, "exportAllPasswords", PropertyKind.BOOLEAN, "false", true);
            addMissingPropertyDescriptor(type, "exportOnlyPasswordPlaceholders", PropertyKind.BOOLEAN, "true",
                    true);
        }
    }

    private void addMissingPropertyDescriptor(Type parentType, String name, PropertyKind kind, String defaultValue,
            boolean hidden) {
        RemoteDescriptor descriptor = (RemoteDescriptor) parentType.getDescriptor();
        RemotePropertyDescriptor pd = (RemotePropertyDescriptor) descriptor.getPropertyDescriptor(name);
        Collection<PropertyDescriptor> propertyDescriptors = descriptor.getPropertyDescriptors();
        if (null == pd) {
            pd = new RemotePropertyDescriptor();
            pd.setKind(kind);
            pd.setDefaultValue(defaultValue);
            pd.setFqn(parentType.toString() + "." + name);
            pd.setName(name);
            // there is no way to set hidden
            if (hidden) {
                pd.setHidden();
            }
        }
        propertyDescriptors.add(pd);
        descriptor.setPropertyDescriptors(propertyDescriptors);
    }

    @Override
    public ConfigurationItem newInstance(String typeName, String name) {
        Type type = getDescriptorRegistry().lookupType(typeName);
        ConfigurationItem ci = newInstance(type, name);
        return ci;
    }

    private ConfigurationItem newInstance(Type type, String id) {
        try {
            RemoteDescriptor remoteDescriptor = (RemoteDescriptor) getDescriptor(type);
            // T ci = remoteDescriptor.newInstance(id);
            BaseConfigurationItem ci;

            if (remoteDescriptor.isAssignableTo(typeForClass(EmbeddedDeployable.class))) {
                ci = new BaseEmbeddedDeployable();
            } else if (remoteDescriptor.isAssignableTo(typeForClass(SourceArtifact.class))) {
                if (remoteDescriptor.isAssignableTo(typeForClass(FolderArtifact.class))) {
                    ci = new BaseDeployableFolderArtifact();
                } else {
                    ci = new BaseDeployableFileArtifact();
                }
            } else if (remoteDescriptor.isAssignableTo(typeForClass(Deployable.class))) {
                ci = new BaseDeployable();
            } else {
                ci = new BaseConfigurationItem();
            }
            ci.setId(id);
            ci.setType(type);

            for (PropertyDescriptor pd : remoteDescriptor.getPropertyDescriptors()) {
                if (pd.isAsContainment()) {
                    String propertyName = pd.getName();
                    if (null == ci.getProperty(propertyName)) {
                        switch (pd.getKind()) {
                        case LIST_OF_CI:
                            ci.setProperty(propertyName, newArrayList());
                            break;
                        case SET_OF_CI:
                            ci.setProperty(propertyName, newHashSet());
                            break;
                        default:
                            break;
                        }
                    }
                }
            }

            initSyntheticProperties(ci, type);

            return ci;
        } catch (Throwable e) {
            String errorMsg = String.format("Unable to instantiate CI '%s' with id '%s'. %s", type, id,
                    e.getMessage());
            throw new RuntimeException(errorMsg, e);
        }
    }

    @Override
    public Collection<Descriptor> getDescriptors() {
        return getDescriptorRegistry().getLoadedDescriptors();
    }

    @Override
    public Descriptor getDescriptor(String type) {
        return getDescriptorRegistry().getLoadedDescriptor(type);
    }

    private Descriptor getDescriptor(Type type) {
        return getDescriptorRegistry().getLoadedDescriptor(type);
    }

    @Override
    public void setProperty(ConfigurationItem ci, String propName, String value) {
        PropertyDescriptor pd = getDescriptor(ci.getType()).getPropertyDescriptor(propName);
        pd.set(ci, convertValue(value, pd));
    }

    private Object convertValue(String val, PropertyDescriptor pd) {
        if (val == null) {
            return null;
        }
        switch (pd.getKind()) {
        case BOOLEAN:
            return Boolean.parseBoolean(val);
        case INTEGER:
            if (val.isEmpty()) {
                return null;
            }
            return Integer.parseInt(val);
        case CI:
            return convertToCiRef(val, pd);
        case SET_OF_STRING:
            return newLinkedHashSet(splitValue(val));
        case LIST_OF_STRING:
            return newArrayList(splitValue(val));
        case SET_OF_CI:
            return newLinkedHashSet(convertToCiRefs(val, pd));
        case LIST_OF_CI:
            return newArrayList(convertToCiRefs(val, pd));
        case MAP_STRING_STRING:
            return Splitter.on('&').withKeyValueSeparator("=").split(val);
        default:
            return val;
        }
    }

    private Iterable<ConfigurationItem> convertToCiRefs(String val, final PropertyDescriptor pd) {
        return FluentIterable.from(splitValue(val)).transform(new Function<String, ConfigurationItem>() {
            @Nullable
            @Override
            public ConfigurationItem apply(@Nullable final String input) {
                return convertToCiRef(input, pd);
            }
        });
    }

    private ConfigurationItem convertToCiRef(String name, PropertyDescriptor pd) {
        BaseConfigurationItem ci = new BaseConfigurationItem();
        ci.setId(name);
        ci.setType(pd.getReferencedType());
        return ci;
    }

    private Iterable<String> splitValue(String val) {
        return Splitter.on(',').trimResults().omitEmptyStrings().split(val);
    }

    private Iterable<Descriptor> getAllDeployableDescriptors() {
        LOCK.enter();
        try {
            if (allDeployableDescriptors == null) {
                Predicate<Descriptor> predicate = Predicates.or(
                        new DescriptorPredicate(typeForName(UDM_DEPLOYABLE)),
                        new DescriptorPredicate(typeForName(UDM_EMBEDDED_DEPLOYABLE)));
                allDeployableDescriptors = FluentIterable.from(getDescriptors()).filter(predicate);
            }
        } finally {
            LOCK.leave();
        }
        return allDeployableDescriptors;
    }

    @Override
    public List<String> getDeployableArtifactTypes() {
        DescriptorPredicate predicate = new DescriptorPredicate(typeForName(UDM_ARTIFACT));
        return toSortedDeployableTypeListing(predicate);
    }

    private List<String> toSortedDeployableTypeListing(Predicate<Descriptor> descriptorsWithType) {
        return FluentIterable.from(getAllDeployableDescriptors()).filter(descriptorsWithType)
                .transform(DESCRIPTOR_TO_TYPE_NAME).toSortedList(Ordering.natural());
    }

    @Override
    public List<String> getDeployableResourceTypes() {
        Predicate<Descriptor> predicate = Predicates
                .not(Predicates.<Descriptor>or(new DescriptorPredicate(typeForName(UDM_ARTIFACT)),
                        new DescriptorPredicate(typeForName(UDM_EMBEDDED_DEPLOYABLE))));
        return toSortedDeployableTypeListing(predicate);
    }

    @Override
    public List<String> getEmbeddedDeployableTypes() {
        DescriptorPredicate predicate = new DescriptorPredicate(typeForName(UDM_EMBEDDED_DEPLOYABLE));
        return toSortedDeployableTypeListing(predicate);
    }

    @Override
    public List<String> getEditablePropertiesForDeployableType(String type) {
        final Type embeddedDeployableType = typeForName(UDM_EMBEDDED_DEPLOYABLE);
        Predicate<PropertyDescriptor> editablePropertyDescriptors = new Predicate<PropertyDescriptor>() {
            @Override
            public boolean apply(PropertyDescriptor pd) {
                return !pd.isHidden() && !pd.getName().equals("tags")
                        && !pd.getName().equals(ArtifactView.FILE_URI_PROPERTY)
                        && !isEmbeddedProperty(pd, embeddedDeployableType);
            }
        };
        return getPropertiesForDeployableType(type, editablePropertyDescriptors);
    }

    @Override
    public List<String> getPropertiesForDeployableType(String type,
            Predicate<PropertyDescriptor> propertyPredicate) {
        Descriptor descriptor = getDescriptor(type);
        return FluentIterable.from(descriptor.getPropertyDescriptors()).filter(propertyPredicate)
                .transform(PROPERTY_DESCRIPTOR_TO_NAME).toSortedList(Ordering.natural());
    }

    @Override
    public void addEmbedded(ConfigurationItem parent, ConfigurationItem embed) {
        com.xebialabs.deployit.plugin.api.reflect.Descriptor descriptor = getDescriptor(
                parent.getType().toString());
        for (PropertyDescriptor pd : descriptor.getPropertyDescriptors()) {
            if (isMatchingEmbeddedProperty(pd, embed.getType())) {
                Collection col = (Collection) pd.get(parent);
                if (col == null) {
                    col = pd.getKind() == PropertyKind.LIST_OF_CI ? newArrayList() : newHashSet();
                    pd.set(parent, col);
                }
                col.add(embed);
                return;
            }
        }
        throw new RuntimeException("Failed to find property that embeds " + embed + " into parent " + parent);
    }

    @Override
    public void reload() {
        LOCK.enter();
        try {
            LOG.warn("About to reload descriptor registry for config: {}.", safeBooterConfigKey());
            version = UUID.randomUUID().toString();
            getDescriptorRegistry().reboot(getCommunicator());
            allDeployableDescriptors = null;
        } finally {
            LOCK.leave();
        }
    }

    @Override
    public String getVersion() {
        return version;
    }

    private boolean isEmbeddedProperty(PropertyDescriptor pd, Type embeddedDeployableType) {
        return pd.isAsContainment()
                && (pd.getKind() == PropertyKind.LIST_OF_CI || pd.getKind() == PropertyKind.SET_OF_CI)
                && pd.getReferencedType().isSubTypeOf(embeddedDeployableType);
    }

    private boolean isMatchingEmbeddedProperty(PropertyDescriptor pd, Type matchType) {
        return pd.isAsContainment()
                && (pd.getKind() == PropertyKind.LIST_OF_CI || pd.getKind() == PropertyKind.SET_OF_CI)
                && pd.getReferencedType().equals(matchType);
    }

    private static final Function<Descriptor, String> DESCRIPTOR_TO_TYPE_NAME = new Function<Descriptor, String>() {
        @Override
        public String apply(Descriptor input) {
            return input.getType().toString();
        }
    };

    private static final Function<PropertyDescriptor, String> PROPERTY_DESCRIPTOR_TO_NAME = new Function<PropertyDescriptor, String>() {
        @Override
        public String apply(PropertyDescriptor input) {
            return input.getName();
        }
    };

    private static class DescriptorPredicate implements Predicate<Descriptor> {

        private Type type;

        DescriptorPredicate(Type type) {
            this.type = type;
        }

        @Override
        public boolean apply(Descriptor input) {
            return input.isAssignableTo(type);
        }
    }
}