Package com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model

Source Code of com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model.BuildMemory$MemoryImprint$Entry

/*
*  The MIT License
*
*  Copyright 2010 Sony Ericsson Mobile Communications. All rights reserved.
*  Copyright 2012 Sony Mobile Communications AB. All rights reserved.
*
*  Permission is hereby granted, free of charge, to any person obtaining a copy
*  of this software and associated documentation files (the "Software"), to deal
*  in the Software without restriction, including without limitation the rights
*  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the Software is
*  furnished to do so, subject to the following conditions:
*
*  The above copyright notice and this permission notice shall be included in
*  all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*  THE SOFTWARE.
*/
package com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model;

import com.sonymobile.tools.gerrit.gerritevents.dto.events.GerritTriggeredEvent;
import com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model.BuildMemory.MemoryImprint.Entry;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritCause;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritTrigger;
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.TriggerContext;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;

import static com.sonyericsson.hudson.plugins.gerrit.trigger.utils.Logic.shouldSkip;

/**
* Keeps track of what builds have been triggered and if all builds are done for specific events.
*
* @author Robert Sandell <robert.sandell@sonyericsson.com>
*/
public class BuildMemory {

    /**
     * Compares GerritTriggeredEvents using the Object.hashCode() method. This ensures that every event received from
     * Gerrit is kept track of individually.
     *
     * @author James E. Blair <jeblair@hp.com>
     */
    private class GerritTriggeredEventComparator implements Comparator<GerritTriggeredEvent> {
        @Override
        public int compare(GerritTriggeredEvent o1, GerritTriggeredEvent o2) {
            return new Integer(((java.lang.Object)o1).hashCode()).compareTo(
                    new Integer(((java.lang.Object)o2).hashCode()));
        }
    }

    private TreeMap<GerritTriggeredEvent, MemoryImprint> memory =
            new TreeMap<GerritTriggeredEvent, MemoryImprint>(
                    new GerritTriggeredEventComparator());
    private static final Logger logger = LoggerFactory.getLogger(BuildMemory.class);

    /**
     * Gets the memory of a specific event.
     *
     * @param event the event.
     * @return the memory.
     */
    public synchronized MemoryImprint getMemoryImprint(GerritTriggeredEvent event) {
        return memory.get(event);
    }

    /**
     * Tells if all triggered builds have started for a specific memory imprint.
     *
     * @param event the event.
     * @return true if it is so.
     */
    public synchronized boolean isAllBuildsCompleted(GerritTriggeredEvent event) {
        MemoryImprint pb = memory.get(event);
        if (pb != null) {
            return pb.isAllBuildsCompleted();
        } else {
            return false;
        }
    }

    /**
     * Gets the statistics of started builds for a specific memory imprint.
     *
     * @param event the event.
     * @return the statistics.
     */
    public synchronized BuildsStartedStats getBuildsStartedStats(GerritTriggeredEvent event) {
        MemoryImprint pb = memory.get(event);
        if (pb != null) {
            return pb.getBuildsStartedStats();
        } else {
            return null;
        }
    }

    /**
     * Returns the status report for the given MemoryImprint.
     *
     * @param event the event.
     * @return the status as it is now.
     *
     * @see MemoryImprint#getStatusReport()
     */
    public synchronized String getStatusReport(GerritTriggeredEvent event) {
        MemoryImprint pb = memory.get(event);
        if (pb != null) {
            return pb.getStatusReport();
        } else {
            return null;
        }
    }

    /**
     * Tells if all triggered builds have started for a specific memory imprint.
     *
     * @param event the event.
     * @return true if it is so.
     */
    public synchronized boolean isAllBuildsStarted(GerritTriggeredEvent event) {
        MemoryImprint pb = memory.get(event);
        if (pb != null) {
            return pb.isAllBuildsSet();
        } else {
            return false;
        }
    }

    /**
     * Sets the memory that a build is completed for an event.
     *
     * @param event the event
     * @param build the build.
     */
    public synchronized void completed(GerritTriggeredEvent event, AbstractBuild build) {
        MemoryImprint pb = memory.get(event);
        if (pb == null) {
            //Shoudn't happen but just in case, keep the memory.
            pb = new MemoryImprint(event);
            memory.put(event, pb);
        }
        pb.set(build.getProject(), build, true);
    }

    /**
     * Sets the memory that a build has started for an event.
     *
     * @param event the event.
     * @param build the build.
     */
    public synchronized void started(GerritTriggeredEvent event, AbstractBuild build) {
        MemoryImprint pb = memory.get(event);
        if (pb == null) {
            //A build should not start for a job that hasn't been registered. Keep the memory anyway.
            pb = new MemoryImprint(event);
            logger.warn("Build started without being registered first.");
            memory.put(event, pb);
        }
        pb.set(build.getProject(), build);
    }

    /**
     * Adds a new memory about a build that has been/will be triggered.
     *
     * @param event   the event that triggered it.
     * @param project the project that was triggered.
     */
    public synchronized void triggered(GerritTriggeredEvent event, AbstractProject project) {
        MemoryImprint pb = memory.get(event);
        if (pb == null) {
            pb = new MemoryImprint(event);
            memory.put(event, pb);
        }
        pb.set(project);
    }

    /**
     * Adds a new memory about a build that has been retriggered. If there is an active memory about the provided event,
     * then the project is reset with no build info. Otherwise the memory is recreated from the list of other builds and
     * their result.
     *
     * @param event       the event to be retriggered.
     * @param project     the project that has been retriggered.
     * @param otherBuilds the list of other builds that was in the "old" memory.
     */
    public synchronized void retriggered(
            GerritTriggeredEvent event,
            AbstractProject project,
            List<AbstractBuild> otherBuilds) {
        MemoryImprint pb = memory.get(event);
        if (pb == null) {
            pb = new MemoryImprint(event);
            memory.put(event, pb);
            if (otherBuilds != null) {
                //It is a new memory so it wasn't building, let's populate with old build info
                for (AbstractBuild build : otherBuilds) {
                    pb.set(build.getProject(), build, !build.isBuilding());
                }
            }
        }
        pb.reset(project);
    }

    /**
     * Removes the memory for the event.
     *
     * @param event the event.
     */
    public synchronized void forget(GerritTriggeredEvent event) {
        memory.remove(event);
    }

    /**
     * Updates the {@link TriggerContext} for the event. The cause and build is the "focal point" for the update, but
     * all memory entities will be updated, but only the current context will be {@link
     * TriggerContext#setThisBuild(hudson.model.AbstractBuild)}updated.
     *
     * @param cause the cause.
     * @param r     the build the cause is in.
     */
    public synchronized void updateTriggerContext(GerritCause cause, AbstractBuild r) {
        MemoryImprint imprint = getMemoryImprint(cause.getEvent());
        TriggerContext context = cause.getContext();
        context.setThisBuild(r);
        for (MemoryImprint.Entry entry : imprint.getEntries()) {
            if (entry.getBuild() != null && !entry.getBuild().equals(r)) {
                context.addOtherBuild(entry.getBuild());
                updateTriggerContext(entry, imprint);
            } else if (entry.getBuild() == null && !entry.getProject().equals(r.getProject())) {
                context.addOtherProject(entry.getProject());
            }
        }
        if (!r.hasntStartedYet() && !r.isBuilding()) {
            try {
                r.save();
            } catch (IOException ex) {
                logger.error("Could not save build state for build " + r, ex);
            }
        }
    }

    /**
     * Updates the {@link TriggerContext} for the provided entry.
     *
     * @param entryToUpdate the entry to update.
     * @param imprint       the information for the update.
     */
    private synchronized void updateTriggerContext(Entry entryToUpdate, MemoryImprint imprint) {
        if (entryToUpdate.getBuild() != null) {
            GerritCause cause = (GerritCause)entryToUpdate.getBuild().getCause(GerritCause.class);
            if (cause != null) {
                TriggerContext context = cause.getContext();
                for (MemoryImprint.Entry ent : imprint.getEntries()) {
                    if (ent.getBuild() != null && !ent.getBuild().equals(entryToUpdate.getBuild())) {
                        context.addOtherBuild(ent.getBuild());
                    } else if (ent.getBuild() == null && !ent.getProject().equals(entryToUpdate.getProject())) {
                        context.addOtherProject(ent.getProject());
                    }
                }
                if (!entryToUpdate.getBuild().hasntStartedYet() && !entryToUpdate.getBuild().isBuilding()) {
                    try {
                        entryToUpdate.getBuild().save();
                    } catch (IOException ex) {
                        logger.error("Could not save state for build " + entryToUpdate.getBuild(), ex);
                    }
                }
            }
        }
    }

    /**
     * Checks in memory if the project has been triggered for the event.
     *
     * @param event   the event.
     * @param project the project.
     * @return true if so.
     */
    public synchronized boolean isTriggered(GerritTriggeredEvent event, AbstractProject project) {
        MemoryImprint pb = memory.get(event);
        if (pb == null) {
            return false;
        } else {
            for (Entry entry : pb.getEntries()) {
                if (entry.getProject().equals(project)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Checks in memory if the project is building the event.
     *
     * @param event   the event.
     * @param project the project.
     * @return true if so.
     */
    public synchronized boolean isBuilding(GerritTriggeredEvent event, AbstractProject project) {
        MemoryImprint pb = memory.get(event);
        if (pb == null) {
            return false;
        } else {
            for (Entry entry : pb.getEntries()) {
                if (entry.getProject().equals(project)) {
                    if (entry.getBuild() != null) {
                        return !entry.isBuildCompleted();
                    } else {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    /**
     * Checks if the provided event exists in this memory.
     *
     * @param event the event to look for.
     * @return true if so.
     */
    public synchronized boolean isBuilding(GerritTriggeredEvent event) {
        MemoryImprint pb = memory.get(event);
        return pb != null;
    }

    /**
     * Returns all started builds in memory for the event.
     *
     * @param event the event.
     * @return the list of builds, or null if there is no memory.
     */
    public synchronized List<AbstractBuild> getBuilds(GerritTriggeredEvent event) {
        MemoryImprint pb = memory.get(event);
        if (pb != null) {
            List<AbstractBuild> list = new LinkedList<AbstractBuild>();
            for (Entry entry : pb.getEntries()) {
                if (entry.getBuild() != null) {
                    list.add(entry.getBuild());
                }
            }
            return list;
        } else {
            return null;
        }
    }

    /**
     * Records the failure message for the given build.
     *
     * @param event          the event.
     * @param r              the build that caused the failure.
     * @param failureMessage the failure message
     */
    public void setEntryFailureMessage(GerritTriggeredEvent event, AbstractBuild r, String failureMessage) {
        MemoryImprint pb = getMemoryImprint(event);

        if (pb != null) {
            Entry entry = pb.getEntry(r.getProject());

            if (entry != null) {
                logger.info("Recording unsuccessful message for {}: {}", event, failureMessage);
                entry.setUnsuccessfulMessage(failureMessage);
            }
        }
    }

    /**
     * A holder for all builds triggered by one event.
     */
    public static class MemoryImprint {

        private GerritTriggeredEvent event;
        private List<Entry> list = new ArrayList<Entry>();

        /**
         * Constructor.
         *
         * @param event the event.
         */
        public MemoryImprint(GerritTriggeredEvent event) {
            this.event = event;
        }

        /**
         * Constructor.
         *
         * @param event   the event.
         * @param project the first project.
         */
        public MemoryImprint(GerritTriggeredEvent event, AbstractProject project) {
            this.event = event;
            set(project);
        }

        /**
         * The event.
         *
         * @return the event.
         */
        public GerritTriggeredEvent getEvent() {
            return event;
        }

        /**
         * A list of Project-Build tuple entries.
         *
         * @return the memory entries.
         */
        public synchronized Entry[] getEntries() {
            return list.toArray(new Entry[list.size()]);
        }

        /**
         * Sets the build to a project or adds the project to the list.
         *
         * @param project the project.
         * @param build   the build.
         */
        protected synchronized void set(AbstractProject project, AbstractBuild build) {
            Entry entry = getEntry(project);
            if (entry == null) {
                entry = new Entry(project, build);
                list.add(entry);
            } else {
                entry.setBuild(build);
            }
        }

        /**
         * Adds the project to the list.
         *
         * @param project the project.
         */
        protected synchronized void set(AbstractProject project) {
            Entry entry = getEntry(project);
            if (entry == null) {
                entry = new Entry(project);
                list.add(entry);
            }
        }

        /**
         * Resets the build info for the project. If the project doesn't exist it would be as if calling {@link
         * #set(hudson.model.AbstractProject)}.
         *
         * @param project the project to reset.
         */
        protected synchronized void reset(AbstractProject project) {
            Entry entry = getEntry(project);
            if (entry == null) {
                entry = new Entry(project);
                list.add(entry);
            } else {
                entry.setBuild(null);
                entry.setBuildCompleted(false);
            }
        }

        /**
         * Sets all the values of an entry and adds it if the project has not been added before.
         *
         * @param project        the project
         * @param build          the build
         * @param buildCompleted if the build is finished.
         */
        private synchronized void set(AbstractProject project, AbstractBuild build, boolean buildCompleted) {
            Entry entry = getEntry(project);
            if (entry == null) {
                entry = new Entry(project, build);
                entry.setBuildCompleted(buildCompleted);
                list.add(entry);
            } else {
                if (entry.getBuild() == null) {
                    entry.setBuild(build);
                }
                entry.setBuildCompleted(buildCompleted);
            }
        }

        /**
         * Tells if all builds have a value (not null).
         *
         * @return true if it is so.
         */
        public synchronized boolean isAllBuildsSet() {
            for (Entry entry : list) {
                if (entry.getBuild() == null) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Tells if all builds have Completed.
         *
         * @return true if it is so.
         */
        public synchronized boolean isAllBuildsCompleted() {
            for (Entry entry : list) {
                if (!entry.isBuildCompleted()) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Returns a string describing the projects and builds status in this memory. Good for logging.
         *
         * @return a report.
         */
        public synchronized String getStatusReport() {
            StringBuilder str = new StringBuilder("");
            for (Entry entry : list) {
                if (entry.getProject() != null) {
                    str.append("  Project/Build: [").append(entry.getProject().getName()).append("]");
                    str.append(": [#");
                    if (entry.getBuild() != null) {
                        str.append(entry.getBuild().getNumber());
                        str.append(": ").append(entry.getBuild().getResult());
                    } else {
                        str.append("XX: NULL");
                    }
                    str.append("] Completed: ").append(entry.isBuildCompleted());
                } else {
                    str.append("  Project/Build: MISSING PROJECT!");
                }
                str.append("\n");
            }
            return str.toString();
        }

        /**
         * Searches the internal list for an entry with the specified project.
         *
         * @param project the project.
         * @return the entry or null if nothis is found.
         */
        private Entry getEntry(AbstractProject project) {
            for (Entry entry : list) {
                if (entry.getProject().equals(project)) {
                    return entry;
                }
            }
            return null;
        }

        /**
         * Gets the statistics about builds started.
         *
         * @return the stats.
         */
        public synchronized BuildsStartedStats getBuildsStartedStats() {
            int started = 0;
            for (Entry entry : list) {
                if (entry.getBuild() != null) {
                    started++;
                }
            }
            return new BuildsStartedStats(event, list.size(), started);
        }

        /**
         * If all entry's results are configured to be skipped.
         *
         * @return true if so.
         * @see #wereAllBuildsSuccessful()
         */
        public synchronized boolean areAllBuildResultsSkipped() {
            for (Entry entry : list) {
                if (entry.getBuild() == null) {
                    return false;
                } else if (!entry.isBuildCompleted()) {
                    return false;
                }
                Result buildResult = entry.getBuild().getResult();
                GerritTrigger trigger = GerritTrigger.getTrigger(entry.getProject());
                if (!shouldSkip(trigger.getSkipVote(), buildResult)) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Tells if all builds in the memory were successful.
         *
         * @return true if it is so, false if not all builds have started or not completed or have any different result
         *         than {@link Result#SUCCESS}.
         */
        public synchronized boolean wereAllBuildsSuccessful() {
            if (areAllBuildResultsSkipped()) {
                for (Entry entry : list) {
                    if (entry.getBuild() == null) {
                        return false;
                    } else if (!entry.isBuildCompleted()) {
                        return false;
                    }
                    Result buildResult = entry.getBuild().getResult();
                    if (buildResult != Result.SUCCESS) {
                        return false;
                    }
                }
            } else {
                for (Entry entry : list) {
                    if (entry.getBuild() == null) {
                        return false;
                    } else if (!entry.isBuildCompleted()) {
                        return false;
                    }
                    Result buildResult = entry.getBuild().getResult();
                    if (buildResult != Result.SUCCESS) {
                        GerritTrigger trigger = GerritTrigger.getTrigger(entry.getProject());
                        if (!shouldSkip(trigger.getSkipVote(), buildResult)) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }

        /**
         * Returns if any started and completed build has the result {@link Result#FAILURE}.
         *
         * @return true if it is so.
         */
        public synchronized boolean wereAnyBuildsFailed() {
            for (Entry entry : list) {
                if (entry.getBuild() != null && entry.isBuildCompleted()
                        && entry.getBuild().getResult() == Result.FAILURE) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Returns if any started and completed build has the result {@link Result#UNSTABLE}.
         *
         * @return true if it is so.
         */
        public synchronized boolean wereAnyBuildsUnstable() {
            for (Entry entry : list) {
                if (entry.getBuild() != null && entry.isBuildCompleted()
                        && entry.getBuild().getResult() == Result.UNSTABLE) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Tells if all builds in the memory were not built.
         *
         * @return true if it is so, false if not all builds have started or not completed or have any different result
         *         than {@link Result#NOT_BUILT}.
         */
        public synchronized boolean wereAllBuildsNotBuilt() {
            for (Entry entry : list) {
                if (entry.getBuild() == null) {
                    return false;
                } else if (!entry.isBuildCompleted()) {
                    return false;
                }
                Result buildResult = entry.getBuild().getResult();
                if (buildResult != Result.NOT_BUILT) {
                    return false;
                }
            }
            return true;
        }

        //CS IGNORE FinalClass FOR NEXT 5 LINES. REASON: Testability.

        /**
         * A project-build entry in the list of a MemoryImprint.
         */
        public static class Entry {

            private AbstractProject project;
            private AbstractBuild build;
            private boolean buildCompleted;
            private String unsuccessfulMessage;

            /**
             * Constructor.
             *
             * @param project the project
             * @param build   the build.
             */
            private Entry(AbstractProject project, AbstractBuild build) {
                this.project = project;
                this.build = build;
                buildCompleted = false;
            }

            /**
             * Constructor.
             *
             * @param project the project.
             */
            private Entry(AbstractProject project) {
                this.project = project;
                buildCompleted = false;
            }

            /**
             * The Project.
             *
             * @return the project.
             */
            public AbstractProject getProject() {
                return project;
            }

            /**
             * The build of a project.
             *
             * @return the build.
             */
            public AbstractBuild getBuild() {
                return build;
            }

            /**
             * The build of a project.
             *
             * @param build the build.
             */
            private void setBuild(AbstractBuild build) {
                this.build = build;
            }

            /**
             * Sets the unsuccessful message for an entry.
             *
             * @param unsuccessfulMessage the message.
             */
            private void setUnsuccessfulMessage(String unsuccessfulMessage) {
                this.unsuccessfulMessage = unsuccessfulMessage;
            }

            /**
             * Gets the unsuccessful message for an entry.
             *
             * @return the message.
             */
            public String getUnsuccessfulMessage() {
                return this.unsuccessfulMessage;
            }

            /**
             * If the build is completed.
             *
             * @return true if the build is completed.
             */
            public boolean isBuildCompleted() {
                return buildCompleted;
            }

            /**
             * If the build is completed.
             *
             * @param buildCompleted true if the build is completed.
             */
            private void setBuildCompleted(boolean buildCompleted) {
                this.buildCompleted = buildCompleted;
            }

        }
    }
}
TOP

Related Classes of com.sonyericsson.hudson.plugins.gerrit.trigger.gerritnotifier.model.BuildMemory$MemoryImprint$Entry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.