org.zeroturnaround.jenkins.LiveRebelProxy.java Source code

Java tutorial

Introduction

Here is the source code for org.zeroturnaround.jenkins.LiveRebelProxy.java

Source

package org.zeroturnaround.jenkins;

/**
 * ***************************************************************
 * Copyright 2011 ZeroTurnaround O
 *
 * 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.
 ****************************************************************
 */
import hudson.FilePath;
import hudson.model.BuildListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipException;

import com.zeroturnaround.liverebel.api.ApplicationInfo;
import com.zeroturnaround.liverebel.api.CommandCenter;
import com.zeroturnaround.liverebel.api.CommandCenterFactory;
import com.zeroturnaround.liverebel.api.ConnectException;
import com.zeroturnaround.liverebel.api.DuplicationException;
import com.zeroturnaround.liverebel.api.Error;
import com.zeroturnaround.liverebel.api.Forbidden;
import com.zeroturnaround.liverebel.api.ParseException;
import com.zeroturnaround.liverebel.api.UploadInfo;
import com.zeroturnaround.liverebel.api.diff.DiffResult;
import com.zeroturnaround.liverebel.api.diff.Level;
import com.zeroturnaround.liverebel.api.update.ConfigurableUpdate;
import com.zeroturnaround.liverebel.util.LiveApplicationUtil;
import com.zeroturnaround.liverebel.util.LiveRebelXml;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.zeroturnaround.jenkins.LiveRebelDeployPublisher.Strategy;
import org.zeroturnaround.zip.ZipUtil;

/**
 * @author Juri Timoshin
 */
public class LiveRebelProxy {

    public static final String ARTIFACT_DEPLOYED_AND_UPDATED = "SUCCESS. Artifact deployed and activated in all %s servers: %s\n";

    private final CommandCenterFactory commandCenterFactory;
    private final BuildListener listener;
    private CommandCenter commandCenter;
    private Strategy strategy;
    private boolean useFallbackIfCompatibleWithWarnings;

    public LiveRebelProxy(CommandCenterFactory centerFactory, BuildListener listener) {
        commandCenterFactory = centerFactory;
        this.listener = listener;
    }

    public boolean perform(FilePath[] wars, String contextPath, List<String> deployableServers, Strategy strategy,
            boolean useFallbackIfCompatibleWithWarnings, boolean uploadOnly, OverrideForm override, File meta)
            throws IOException, InterruptedException {
        if (wars.length == 0) {
            listener.getLogger()
                    .println("Could not find any artifact to deploy. Please, specify it in job configuration.");
            return false;
        }

        if (deployableServers.isEmpty()) {
            listener.getLogger().println("No servers specified in LiveRebel configuration.");
            return false;
        }

        this.strategy = strategy;
        this.useFallbackIfCompatibleWithWarnings = useFallbackIfCompatibleWithWarnings;

        if (!initCommandCenter()) {
            return false;
        }

        listener.getLogger().println("Deploying artifacts.");
        for (FilePath warFile : wars) {
            boolean result = false;
            Boolean tempFileCreated = false;
            ArrayList<File> filestToDelete = new ArrayList<File>();
            try {
                listener.getLogger().printf("Processing artifact: %s\n", warFile);

                if (override != null && (override.getApp() != null || override.getVer() != null)) {
                    String app = noramlizeString(override.getApp());
                    String ver = noramlizeString(override.getVer());
                    warFile = overrideOrCreateXML(new File(warFile.getRemote()), app, ver);
                    filestToDelete.add(new File(warFile.getRemote()));
                    tempFileCreated = true;
                }

                if (meta != null) {
                    warFile = addMetadataIntoArchive(new File(warFile.getRemote()), meta);
                    filestToDelete.add(new File(warFile.getRemote()));
                    tempFileCreated = true;
                }

                LiveRebelXml lrXml = getLiveRebelXml(warFile);
                ApplicationInfo applicationInfo = getCommandCenter().getApplication(lrXml.getApplicationId());
                uploadIfNeeded(applicationInfo, lrXml.getVersionId(), warFile);
                if (!uploadOnly) {
                    update(lrXml, applicationInfo, warFile, deployableServers, contextPath);
                    listener.getLogger().printf(ARTIFACT_DEPLOYED_AND_UPDATED, deployableServers, warFile);
                }
                result = true;
            } catch (IllegalArgumentException e) {
                listener.getLogger().println("ERROR!");
                e.printStackTrace(listener.getLogger());
            } catch (Error e) {
                listener.getLogger().println("ERROR! Unexpected error received from server.");
                listener.getLogger().println();
                listener.getLogger().println("URL: " + e.getURL());
                listener.getLogger().println("Status code: " + e.getStatus());
                listener.getLogger().println("Message: " + e.getMessage());
            } catch (ParseException e) {
                listener.getLogger().println("ERROR! Unable to read server response.");
                listener.getLogger().println();
                listener.getLogger().println("Response: " + e.getResponse());
                listener.getLogger().println("Reason: " + e.getMessage());
            } catch (RuntimeException e) {
                if (e.getCause() instanceof ZipException) {
                    listener.getLogger().printf(
                            "ERROR! Unable to read artifact (%s). The file you trying to deploy is not an artifact or may be corrupted.\n",
                            warFile);
                } else {
                    listener.getLogger().println("ERROR! Unexpected error occured:");
                    listener.getLogger().println();
                    e.printStackTrace(listener.getLogger());
                }
            } catch (Throwable t) {
                listener.getLogger().println("ERROR! Unexpected error occured:");
                listener.getLogger().println();
                t.printStackTrace(listener.getLogger());
            } finally {
                if (tempFileCreated) {
                    for (File file : filestToDelete) {
                        FileUtils.deleteQuietly(file);
                    }
                }
            }
            if (!result)
                return result;
        }
        return true;
    }

    private String noramlizeString(String name) {
        if (name == null || name.trim().equals("")) {
            name = null;
        }
        return name;
    }

    boolean initCommandCenter() {
        try {
            this.commandCenter = commandCenterFactory.newCommandCenter();
            return true;
        } catch (Forbidden e) {
            listener.getLogger().println(
                    "ERROR! Access denied. Please, navigate to Jenkins Configuration to specify LiveRebel Authentication Token.");
            return false;
        } catch (ConnectException e) {
            listener.getLogger().println("ERROR! Unable to connect to server.");
            listener.getLogger().println();
            listener.getLogger().println("URL: " + e.getURL());
            if (e.getURL().equals("https://")) {
                listener.getLogger()
                        .println("Please, navigate to Jenkins Configuration to specify running LiveRebel Url.");
            } else {
                listener.getLogger().println("Reason: " + e.getMessage());
            }
            return false;
        }
    }

    boolean isFirstRelease(ApplicationInfo applicationInfo) {
        return applicationInfo == null;
    }

    void update(LiveRebelXml lrXml, ApplicationInfo applicationInfo, FilePath warFile, List<String> selectedServers,
            String contextPath) throws IOException, InterruptedException {
        listener.getLogger().println("Starting updating application on servers:");

        Set<String> deployServers = getDeployServers(applicationInfo, selectedServers);
        if (!deployServers.isEmpty()) {
            deploy(lrXml, warFile, deployServers, contextPath);
        }

        if (deployServers.size() != selectedServers.size()) {
            Set<String> activateServers = new HashSet<String>(selectedServers);
            activateServers.removeAll(deployServers);

            Level diffLevel = getMaxDifferenceLevel(applicationInfo, lrXml, activateServers);

            activate(lrXml, warFile, activateServers, diffLevel);
        }
    }

    void deploy(LiveRebelXml lrXml, FilePath warfile, Set<String> serverIds, String contextPath) {
        listener.getLogger().printf("Deploying new application on %s.\n", serverIds);
        if (contextPath == null || contextPath.equals(""))
            contextPath = null;
        getCommandCenter().deploy(lrXml.getApplicationId(), lrXml.getVersionId(), contextPath, serverIds);
        listener.getLogger().printf("SUCCESS: Application deployed to %s.\n", serverIds);
    }

    void activate(LiveRebelXml lrXml, FilePath warfile, Set<String> serverIds, Level diffLevel)
            throws IOException, InterruptedException {
        ConfigurableUpdate update = getCommandCenter().update(lrXml.getApplicationId(), lrXml.getVersionId());
        if (diffLevel == Level.ERROR || diffLevel == Level.WARNING && useFallbackIfCompatibleWithWarnings) {
            if (strategy == Strategy.OFFLINE)
                update.enableOffline();
            else if (strategy == Strategy.ROLLING)
                update.enableRolling();
        }
        update.on(serverIds);
        update.execute();
    }

    DiffResult getDifferences(LiveRebelXml lrXml, String activeVersion) {
        DiffResult diffResult = getCommandCenter().compare(lrXml.getApplicationId(), activeVersion,
                lrXml.getVersionId(), false);
        diffResult.print(listener.getLogger());
        listener.getLogger().println();
        return diffResult;
    }

    void uploadIfNeeded(ApplicationInfo applicationInfo, String currentVersion, FilePath warFile)
            throws IOException, InterruptedException {
        if (applicationInfo != null && applicationInfo.getVersions().contains(currentVersion)) {
            listener.getLogger().println("Current version of application is already uploaded. Skipping upload.");
        } else {
            uploadArtifact(new File(warFile.getRemote()));
            listener.getLogger().printf("Artifact uploaded: %s\n", warFile);
        }
    }

    boolean uploadArtifact(File artifact) throws IOException, InterruptedException {
        try {
            UploadInfo upload = getCommandCenter().upload(artifact);
            listener.getLogger().printf("SUCCESS: %s %s was uploaded.\n", upload.getApplicationId(),
                    upload.getVersionId());
            return true;
        } catch (DuplicationException e) {
            listener.getLogger().println(e.getMessage());
            return false;
        }
    }

    LiveRebelXml getLiveRebelXml(FilePath warFile) throws IOException, InterruptedException {
        LiveRebelXml lrXml = LiveApplicationUtil.findLiveRebelXml(new File(warFile.getRemote()));
        if (lrXml != null) {
            listener.getLogger().printf("Found LiveRebel xml. Current application is: %s %s.\n",
                    lrXml.getApplicationId(), lrXml.getVersionId());
            if (lrXml.getApplicationId() == null) {
                throw new RuntimeException("application name is not set in liverebel.xml");
            }
            if (lrXml.getVersionId() == null) {
                throw new RuntimeException("application version is not set in liverebel.xml");
            }
            return lrXml;
        } else {
            throw new RuntimeException("Didn't find liverebel.xml");
        }
    }

    Set<String> getDeployServers(ApplicationInfo applicationInfo, List<String> selectedServers) {
        Set<String> deployServers = new HashSet<String>();

        if (isFirstRelease(applicationInfo)) {
            deployServers.addAll(selectedServers);
            return deployServers;
        }

        Map<String, String> activeVersions = applicationInfo.getActiveVersionPerServer();

        for (String server : selectedServers) {
            if (!activeVersions.containsKey(server))
                deployServers.add(server);
        }
        return deployServers;
    }

    private Level getMaxDifferenceLevel(ApplicationInfo applicationInfo, LiveRebelXml lrXml,
            Set<String> serversToUpdate) {
        Map<String, String> activeVersions = applicationInfo.getActiveVersionPerServer();
        Level diffLevel = Level.NOP;
        String versionToUpdateTo = lrXml.getVersionId();
        int serversWithSameVersion = 0;
        for (Entry<String, String> entry : activeVersions.entrySet()) {
            String server = entry.getKey();
            if (!serversToUpdate.contains(server)) {
                continue;
            }
            String versionInServer = entry.getValue();
            if (StringUtils.equals(versionToUpdateTo, versionInServer)) {
                serversWithSameVersion++;
                serversToUpdate.remove(server);
                listener.getLogger().println("Server " + server + " already contains active version "
                        + lrXml.getVersionId() + " of application " + lrXml.getApplicationId());
            } else {
                DiffResult differences = getDifferences(lrXml, versionInServer);
                Level maxLevel = differences.getMaxLevel();
                if (maxLevel.compareTo(diffLevel) > 0) {
                    diffLevel = maxLevel;
                }
            }
        }
        if (serversWithSameVersion > 0) {
            String msg = "Cancelling update - version " + lrXml.getVersionId() + " of application "
                    + lrXml.getApplicationId() + " is already deployed to " + serversWithSameVersion + " servers";
            if (!serversToUpdate.isEmpty()) {
                msg += " out of " + (serversToUpdate.size() + serversWithSameVersion) + " servers.";
            }
            throw new RuntimeException(msg);
        }
        return diffLevel;
    }

    /**
     * @return the commandCenter
     */
    public CommandCenter getCommandCenter() {
        return commandCenter;
    }

    protected FilePath overrideOrCreateXML(File file, String app, String ver)
            throws IOException, InterruptedException {
        if (!file.isFile())
            throw new IllegalArgumentException("File not found: " + file.getAbsolutePath());
        LiveRebelXml initialArchiveXml = getLiveRebelXml(new FilePath(file));
        FilePath tempFile = null;
        if (initialArchiveXml == null) {
            tempFile = createLiveRebelXml(file, app, ver);
        } else {
            tempFile = overrideLiveRebelXml(file, app, ver, initialArchiveXml);
        }
        return tempFile;
    }

    private FilePath overrideLiveRebelXml(File file, String app, String ver, LiveRebelXml initialArchiveXml) {
        LiveRebelXml newArchiveXml;
        if (app != null && ver != null) {
            newArchiveXml = new LiveRebelXml(app, ver);
        } else if (app == null) {
            newArchiveXml = new LiveRebelXml(initialArchiveXml.getApplicationId(), ver);
        } else {
            newArchiveXml = new LiveRebelXml(app, initialArchiveXml.getVersionId());
        }
        return new FilePath(addLiveRebelXml(file, newArchiveXml));
    }

    private FilePath createLiveRebelXml(File file, String app, String ver) {
        if (app == null || ver == null) {
            throw new IllegalArgumentException(
                    "Archive didn't contain liverebel.xml, but creation failed, because not enough information was given!"
                            + "\n" + "both Application name and version must be specified");
        }

        LiveRebelXml xml = new LiveRebelXml(app, ver);
        return new FilePath(addLiveRebelXml(file, xml));

    }

    public String sanitize(String name) {
        StringBuffer sb = new StringBuffer(name);
        for (int i = 0; i < sb.length(); i++)
            if (!Character.isLetterOrDigit(sb.charAt(i)))
                sb.setCharAt(i, '-');
        sb.append("-");
        sb.append(DigestUtils.md5Hex(name));
        return sb.toString();
    }

    public File addLiveRebelXml(File file, LiveRebelXml xml) {
        String destPath = file.getParentFile().getAbsolutePath() + "/"
                + sanitize(xml.getApplicationId() + "-" + xml.getVersionId() + "-" + System.currentTimeMillis());
        File destFile = new File(destPath);

        byte[] bytes;
        try {
            bytes = xml.getAsXml().getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        String liverebelXml = "WEB-INF/classes/liverebel.xml";
        if (ZipUtil.containsEntry(file, liverebelXml)) {
            ZipUtil.replaceEntry(file, liverebelXml, bytes, destFile);
        } else {
            ZipUtil.addEntry(file, liverebelXml, bytes, destFile);
        }
        return destFile;
    }

    private FilePath addMetadataIntoArchive(File archive, File metaData) {
        if (!metaData.isFile())
            throw new IllegalArgumentException("File not found: " + metaData.getAbsolutePath());
        if (!archive.isFile())
            throw new IllegalArgumentException("File not found: " + archive.getAbsolutePath());

        return new FilePath(addMetaData(archive, metaData));
    }

    private File addMetaData(File archive, File metaData) {
        String destPath = archive.getParentFile().getAbsolutePath() + "/" + archive.getName() + "-"
                + System.currentTimeMillis();
        File destFile = new File(destPath);
        byte bytes[];
        try {
            FileInputStream fin = new FileInputStream(metaData);
            bytes = new byte[(int) metaData.length()];
            fin.read(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        String metadata = "WEB-INF/metadata.txt";
        if (ZipUtil.containsEntry(archive, metadata)) {
            ZipUtil.replaceEntry(archive, metadata, bytes, destFile);
        } else {
            ZipUtil.addEntry(archive, metadata, bytes, destFile);
        }
        return destFile;
    }
}