jp.co.cyberagent.jenkins.plugins.AndroidAppZonePublisher.java Source code

Java tutorial

Introduction

Here is the source code for jp.co.cyberagent.jenkins.plugins.AndroidAppZonePublisher.java

Source

/*
 * Copyright (C) 2013 CyberAgent
 *
 * 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 jp.co.cyberagent.jenkins.plugins;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.scm.ChangeLogSet;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import net.sf.json.JSONObject;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class AndroidAppZonePublisher extends Notifier {
    public static final String DEFAULT_APPSERVER = "https://appzone.xxxx.com/api/";

    private static final String TAG = "[AppZone] ";

    private final String id;
    private final String tag;

    private final boolean prependNameToTag;

    // Fields in config.jelly must match the parameter names in the
    // "DataBoundConstructor"
    @DataBoundConstructor
    public AndroidAppZonePublisher(final String id, final String tag, final boolean prependNameToTag) {
        this.id = id;
        this.tag = tag;
        this.prependNameToTag = prependNameToTag;
    }

    public String getId() {
        return id;
    }

    public String getTag() {
        return tag;
    }

    public boolean getPrependNameToTag() {
        return prependNameToTag;
    }

    public FormValidation doCheckId(@QueryParameter final String value) throws IOException, ServletException {
        if (value.length() == 0) {
            return FormValidation.error("ID needs to be set.");
        }
        return FormValidation.ok();
    }

    @Override
    public boolean perform(final AbstractBuild build, final Launcher launcher, final BuildListener listener) {
        StringBuilder changeLog = new StringBuilder();
        for (Object changeObject : build.getChangeSet().getItems()) {
            ChangeLogSet.Entry change = (ChangeLogSet.Entry) changeObject;
            if (changeLog.length() > 0) {
                changeLog.append("\n");
            }
            changeLog.append(change.getMsg() + " (" + change.getAuthor().getDisplayName() + ")");
        }
        String server = getDescriptor().getServer();
        if (server == null || server.length() == 0) {
            listener.getLogger().println(TAG + "AppZone server not set. Please set in global config! Aborting.");
            return false;
        }

        List<FilePath> files = getPossibleAppFiles(build, listener);
        if (files.isEmpty()) {
            listener.getLogger().println(TAG + "No file to publish found. Skip.");
            return true;
        }
        Iterator<FilePath> fileIterator = files.iterator();
        while (fileIterator.hasNext()) {
            try {
                FilePath file = fileIterator.next();
                String fileName = file.getName();
                DeployStrategy deploy;
                listener.getLogger().println(TAG + "File: " + fileName);
                if (fileName.endsWith(".apk")) {
                    deploy = new DeployStrategyAndroid(server, id, tag, prependNameToTag, file, build, listener);
                } else if (fileName.endsWith(".ipa")) {
                    deploy = new DeployStrategyIOs(server, id, tag, prependNameToTag, file, build, listener);
                } else {
                    return false;
                }
                deploy.setApiKey(getDescriptor().getApikey());
                deploy.setChangeLog(changeLog.toString());
                listener.getLogger().println(TAG + "Version: " + deploy.getVersion());
                listener.getLogger().println(TAG + "Publishing to: " + deploy.getUrl());

                setUpSsl();
                HttpClient httpclient = new HttpClient();
                PostMethod filePost = new PostMethod(deploy.getUrl());
                List<Part> parts = deploy.getParts();
                filePost.setRequestEntity(
                        new MultipartRequestEntity(parts.toArray(new Part[parts.size()]), filePost.getParams()));
                httpclient.executeMethod(filePost);
                int statusCode = filePost.getStatusCode();
                if (statusCode < 200 || statusCode > 299) {
                    String body = filePost.getResponseBodyAsString();
                    listener.getLogger().println(TAG + "Response (" + statusCode + "):" + body);
                    return false;
                }
            } catch (IOException e) {
                listener.getLogger().print(e.getMessage());
                return false;
            }
        }
        return true;
    }

    private void setUpSsl() {
        // TODO maybe have setting or given certificate to check with.
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
            SSLContext.setDefault(ctx);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List<FilePath> getPossibleAppFiles(final AbstractBuild build, final BuildListener listener) {
        try {
            List<FilePath> files = FileListUtil.listFilesRecursively(build.getWorkspace(),
                    new RegexFileFilter("(.(?!unaligned)(?!unsigned))*(\\.apk|\\.ipa)"));

            List<FilePath> removeFiles = new LinkedList<FilePath>();
            for (FilePath file : files) {
                String fileAbsolutePath = file.getRemote();
                if (fileAbsolutePath.endsWith(".apk")) {
                    FilePath propertiesFile = new FilePath(file.getParent().getParent(), "project.properties");
                    try {
                        if (propertiesFile.exists()) {
                            boolean isLibrary = propertiesFile.readToString().contains("android.library=true");
                            if (isLibrary) {
                                removeFiles.add(file);
                            }
                        } else if (!fileAbsolutePath.endsWith("/build/apk/" + file.getName())) {
                            removeFiles.add(file);
                        }
                    } catch (Exception e) {
                        removeFiles.add(file);
                    }
                }
            }
            files.removeAll(removeFiles);
            return files;
        } catch (Exception e) {
            listener.getLogger().println(TAG + "Error: " + e.getMessage());
        }
        return new LinkedList<FilePath>();
    }

    // Overridden for better type safety.
    // If your plugin doesn't really define any property on Descriptor,
    // you don't have to do this.
    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    @Override
    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.BUILD;
    }

    @Extension
    // This indicates to Jenkins that this is an implementation of an extension
    // point.
    public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {

        private String server;
        private String apikey;

        public DescriptorImpl() {
            super(AndroidAppZonePublisher.class);
            load();
        }

        public FormValidation doCheckServer(@QueryParameter final String value)
                throws IOException, ServletException {
            if (value.length() != 0 && !value.startsWith("http")) {
                return FormValidation.error("Server needs to start with http");
            }
            return FormValidation.ok();
        }

        @Override
        public boolean isApplicable(final Class<? extends AbstractProject> aClass) {
            // Indicates that this builder can be used with all kinds of project
            // types
            return true;
        }

        /**
         * This human readable name is used in the configuration screen.
         */
        @Override
        public String getDisplayName() {
            return "Publish to AppZone";
        }

        @Override
        public boolean configure(final StaplerRequest req, final JSONObject formData) throws FormException {
            req.bindJSON(this, formData);
            save();
            return super.configure(req, formData);
        }

        public String getServer() {
            if (server == null || server.length() == 0) {
                server = DEFAULT_APPSERVER;
            }
            return server;
        }

        public void setServer(final String server) {
            this.server = server;
        }

        public String getApikey() {
            return apikey;
        }

        public void setApikey(final String apiKey) {
            this.apikey = apiKey;
        }
    }

    private static class DefaultTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(final X509Certificate[] arg0, final String arg1)
                throws CertificateException {
        }

        @Override
        public void checkServerTrusted(final X509Certificate[] arg0, final String arg1)
                throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }
}