io.relution.jenkins.awssqs.SQSTrigger.java Source code

Java tutorial

Introduction

Here is the source code for io.relution.jenkins.awssqs.SQSTrigger.java

Source

/*
 * Copyright 2016 M-Way Solutions GmbH
 *
 * 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 io.relution.jenkins.awssqs;

import com.amazonaws.services.sqs.model.Message;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.Util;
import hudson.console.AnnotatedLargeText;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Cause;
import hudson.model.Item;
import hudson.model.ParametersAction;
import hudson.model.StringParameterValue;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.SequentialExecutionQueue;
import io.relution.jenkins.awssqs.i18n.sqstrigger.Messages;
import io.relution.jenkins.awssqs.interfaces.EventTriggerMatcher;
import io.relution.jenkins.awssqs.interfaces.MessageParserFactory;
import io.relution.jenkins.awssqs.interfaces.SQSQueue;
import io.relution.jenkins.awssqs.interfaces.SQSQueueMonitorScheduler;
import io.relution.jenkins.awssqs.logging.Log;
import io.relution.jenkins.awssqs.model.events.ConfigurationChangedEvent;
import io.relution.jenkins.awssqs.model.events.EventBroker;
import net.sf.json.JSONObject;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.lang3.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SQSTrigger extends Trigger<AbstractProject<?, ?>>
        implements io.relution.jenkins.awssqs.interfaces.SQSQueueListener, Runnable {

    private final String queueUuid;

    private transient SQSQueueMonitorScheduler scheduler;

    private transient MessageParserFactory messageParserFactory;
    private transient EventTriggerMatcher eventTriggerMatcher;

    private transient ExecutorService executor;

    @DataBoundConstructor
    public SQSTrigger(final String queueUuid) {
        this.queueUuid = queueUuid;
    }

    public File getLogFile() {
        if (this.job == null) {
            return null;
        } else {
            return new File(this.job.getRootDir(), "sqs-polling.log");
        }
    }

    @Override
    public void start(final AbstractProject<?, ?> project, final boolean newInstance) {
        super.start(project, newInstance);

        final DescriptorImpl descriptor = (DescriptorImpl) this.getDescriptor();
        descriptor.queue.execute(new Runnable() {

            @Override
            public void run() {
                Log.info("Start trigger for project %s", project);
                SQSTrigger.this.getScheduler().register(SQSTrigger.this);
            }
        });
    }

    @Override
    public void run() {
        final SQSTriggerBuilder builder = new SQSTriggerBuilder(this, this.job);
        builder.run();
    }

    @Override
    public void stop() {
        super.stop();

        final DescriptorImpl descriptor = (DescriptorImpl) this.getDescriptor();
        descriptor.queue.execute(new Runnable() {

            @Override
            public void run() {
                Log.info("Stop trigger (%s)", this);
                SQSTrigger.this.getScheduler().unregister(SQSTrigger.this);
            }
        });
    }

    @Override
    public Collection<? extends Action> getProjectActions() {
        return Collections.singleton(new SQSTriggerPollingAction());
    }

    @Override
    public void handleMessages(final List<Message> messages) {
        for (final Message message : messages) {
            this.handleMessage(message);
        }
    }

    @Override
    public String getQueueUuid() {
        return this.queueUuid;
    }

    @Inject
    public void setScheduler(final SQSQueueMonitorScheduler scheduler) {
        this.scheduler = scheduler;
    }

    public SQSQueueMonitorScheduler getScheduler() {
        if (this.scheduler == null) {
            io.relution.jenkins.awssqs.Context.injector().injectMembers(this);
        }
        return this.scheduler;
    }

    @Inject
    public void setMessageParserFactory(final MessageParserFactory factory) {
        this.messageParserFactory = factory;
    }

    public MessageParserFactory getMessageParserFactory() {
        if (this.messageParserFactory == null) {
            io.relution.jenkins.awssqs.Context.injector().injectMembers(this);
        }
        return this.messageParserFactory;
    }

    @Inject
    public void setEventTriggerMatcher(final EventTriggerMatcher matcher) {
        this.eventTriggerMatcher = matcher;
    }

    public EventTriggerMatcher getEventTriggerMatcher() {
        if (this.eventTriggerMatcher == null) {
            io.relution.jenkins.awssqs.Context.injector().injectMembers(this);
        }
        return this.eventTriggerMatcher;
    }

    @Inject
    public void setExecutorService(final ExecutorService executor) {
        this.executor = executor;
    }

    public ExecutorService getExecutorService() {
        if (this.executor == null) {
            io.relution.jenkins.awssqs.Context.injector().injectMembers(this);
        }
        return this.executor;
    }

    public String getCurrentQueueUrl() {
        TriggerDescriptor descriptor = this.getDescriptor();
        if (descriptor instanceof DescriptorImpl) {
            DescriptorImpl impl = (DescriptorImpl) descriptor;
            SQSQueue queue = impl.getSqsQueue(getQueueUuid());
            return queue.getUrl();
        }
        throw new RuntimeException("TriggerDescriptor should be instance of DescriptorImpl");
    }

    private void handleMessage(final Message message) {
        Log.info("Message received...");
        Map<String, String> jobParams = new HashMap<>();

        // add job parameters from the message (N.B. won't work post Jenkins v2+) @see https://wiki.jenkins-ci.org/display/JENKINS/Plugins+affected+by+fix+for+SECURITY-170
        for (Map.Entry<String, String> att : message.getAttributes().entrySet()) {
            if (StringUtils.isNotBlank(att.getKey())) {
                jobParams.put("sqs_" + att.getKey(), att.getValue());
            }
        }
        jobParams.put("sqs_body", message.getBody());
        jobParams.put("sqs_messageId", message.getMessageId());
        jobParams.put("sqs_receiptHandle", message.getReceiptHandle());
        jobParams.put("sqs_bodyMD5", message.getMD5OfBody());
        jobParams.put("sqs_queueUrl", getCurrentQueueUrl());
        startJob(jobParams);

        //        final MessageParser parser = this.messageParserFactory.createParser(message);
        //        final EventTriggerMatcher matcher = this.getEventTriggerMatcher();
        //        final List<ExecuteJenkinsJobEvent> events = parser.parseMessage(message);
        //
        //        if (matcher.matches(events, this.job)) {
        //            this.execute();
        //        }else{
        //            Log.info("Executing handleMessage when no event is matched");
        //            this.execute();
        //        }
    }

    private void startJob(Map<String, String> jobParameters) {
        // initialise parameters...
        List<StringParameterValue> params = new ArrayList<>();
        for (Map.Entry<String, String> param : jobParameters.entrySet()) {
            params.add(new StringParameterValue(param.getKey(), param.getValue()));
        }

        // setup default cause...
        Cause cause = new Cause.RemoteCause("NOT_SET", "Job triggered by AWS SQS Message");

        // attempt to build cause from descriptor...
        TriggerDescriptor descriptor = this.getDescriptor();
        if (descriptor instanceof DescriptorImpl) {
            DescriptorImpl impl = (DescriptorImpl) descriptor;
            List<io.relution.jenkins.awssqs.SQSTriggerQueue> queues = impl.getSqsQueues();
            if (!queues.isEmpty()) {
                cause = new Cause.RemoteCause(queues.get(0).getUrl(), "Job triggered by AWS SQS Message");
            }
        }

        StringParameterValue[] parameters = params.toArray(new StringParameterValue[params.size()]);
        Log.info("Triggering job with %s parameters...", parameters.length);
        if (job == null) {
            Log.severe("Unexpected error, 'job' object was null!");
        } else {
            job.scheduleBuild(0, cause, new ParametersAction(parameters));
        }
        Log.info("Triggering job [COMPLETED]");
    }

    //    private void execute() {
    //        Log.info("SQS event triggered build of %s", this.job.getFullDisplayName());
    //        this.executor.execute(this);
    //    }

    public final class SQSTriggerPollingAction implements Action {

        public AbstractProject<?, ?> getOwner() {
            return SQSTrigger.this.job;
        }

        @Override
        public String getIconFileName() {
            return "clipboard.png";
        }

        @Override
        public String getDisplayName() {
            return "SQS Activity Log";
        }

        @Override
        public String getUrlName() {
            return "SQSActivityLog";
        }

        public String getLog() throws IOException {
            return Util.loadFile(SQSTrigger.this.getLogFile());
        }

        public void writeLogTo(final XMLOutput out) throws IOException {
            final AnnotatedLargeText<?> log = new AnnotatedLargeText<SQSTriggerPollingAction>(
                    SQSTrigger.this.getLogFile(), Charset.defaultCharset(), true, this);

            log.writeHtmlTo(0, out.asWriter());
        }
    }

    @Extension
    public static final class DescriptorImpl extends TriggerDescriptor {

        private static final String KEY_SQS_QUEUES = "sqsQueues";
        private volatile List<io.relution.jenkins.awssqs.SQSTriggerQueue> sqsQueues;

        private volatile transient Map<String, io.relution.jenkins.awssqs.SQSTriggerQueue> sqsQueueMap;
        private transient boolean isLoaded;

        private transient final SequentialExecutionQueue queue = new SequentialExecutionQueue(
                Executors.newSingleThreadExecutor());

        public static DescriptorImpl get() {
            final DescriptorExtensionList<Trigger<?>, TriggerDescriptor> triggers = Trigger.all();
            return triggers.get(DescriptorImpl.class);
        }

        public DescriptorImpl() {
            super(SQSTrigger.class);
        }

        @Override
        public synchronized void load() {
            super.load();
            this.initQueueMap();
            this.isLoaded = true;
        }

        @Override
        public boolean isApplicable(final Item item) {
            return item instanceof AbstractProject;
        }

        @Override
        public String getDisplayName() {
            return Messages.displayName();
        }

        public ListBoxModel doFillQueueUuidItems() {
            final List<io.relution.jenkins.awssqs.SQSTriggerQueue> queues = this.getSqsQueues();
            final ListBoxModel items = new ListBoxModel();

            for (final io.relution.jenkins.awssqs.SQSTriggerQueue queue : queues) {
                items.add(queue.getName(), queue.getUuid());
            }

            return items;
        }

        public FormValidation doCheckQueueUuid(@QueryParameter final String value) {
            if (this.getSqsQueues().size() == 0) {
                return FormValidation.error(Messages.errorQueueUnavailable());
            }

            if (StringUtils.isEmpty(value)) {
                return FormValidation.ok(Messages.infoQueueDefault());
            }

            final SQSQueue queue = this.getSqsQueue(value);

            if (queue == null) {
                return FormValidation.error(Messages.errorQueueUuidUnknown());
            }

            return FormValidation.ok();
        }

        @Override
        public boolean configure(final StaplerRequest req, final JSONObject json) throws FormException {
            final Object sqsQueues = json.get(KEY_SQS_QUEUES);

            this.sqsQueues = req.bindJSONToList(io.relution.jenkins.awssqs.SQSTriggerQueue.class, sqsQueues);
            this.initQueueMap();
            this.save();

            EventBroker.getInstance().post(new ConfigurationChangedEvent());
            return true;
        }

        public List<io.relution.jenkins.awssqs.SQSTriggerQueue> getSqsQueues() {
            if (!this.isLoaded) {
                this.load();
            }
            if (this.sqsQueues == null) {
                return Collections.emptyList();
            }
            return this.sqsQueues;
        }

        public SQSQueue getSqsQueue(final String uuid) {
            if (!this.isLoaded) {
                this.load();
            }
            if (this.sqsQueueMap == null) {
                return null;
            }
            return this.sqsQueueMap.get(uuid);
        }

        private void initQueueMap() {
            if (this.sqsQueues == null) {
                return;
            }

            this.sqsQueueMap = Maps.newHashMapWithExpectedSize(this.sqsQueues.size());

            for (final io.relution.jenkins.awssqs.SQSTriggerQueue queue : this.sqsQueues) {
                this.sqsQueueMap.put(queue.getUuid(), queue);
            }
        }
    }
}