All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.aequologica.neo.dagr.DagOnSteroids Maven / Gradle / Ivy

The newest version!
package net.aequologica.neo.dagr;

import static net.aequologica.neo.dagr.DagOnSteroids.NodeCleaner.NodeState.CLEAN;
import static net.aequologica.neo.dagr.DagOnSteroids.NodeCleaner.NodeState.CLEANED;
import static net.aequologica.neo.dagr.DagOnSteroids.NodeCleaner.NodeState.DIRTY;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.CLEAN_ABORTED;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.CLEAN_ERROR;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.CLEAN_OK;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.CLEAN_ORDER_ERROR;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.CLEAN_ORDER_OK;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.CLEAN_STARTED;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.INITIALIZE;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.MAGIC_CLEAN;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.SMUDGE;
import static net.aequologica.neo.dagr.bus.BusEvent.Type.TERMINATE;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import net.aequologica.neo.dagr.DagOnSteroids.DagCleaner.Journal;
import net.aequologica.neo.dagr.DagOnSteroids.NodeCleaner.NodeCleanerException;
import net.aequologica.neo.dagr.DagOnSteroids.NodeCleaner.NodeCleaningResult;
import net.aequologica.neo.dagr.DagOnSteroids.NodeCleaner.NodeState;
import net.aequologica.neo.dagr.bus.Bus;
import net.aequologica.neo.dagr.model.Dag;
import net.aequologica.neo.dagr.model.Dag.Bump;
import net.aequologica.neo.dagr.model.Dag.Bumper;
import net.aequologica.neo.dagr.model.Dag.Gucrid;
import net.aequologica.neo.dagr.model.Dag.Node;

import de.skuzzle.semantic.Version;
import com.pivovarit.function.ThrowingBiFunction;

public class DagOnSteroids {

    private static final Logger           LOG        = LoggerFactory.getLogger(DagOnSteroids.class);
    private static final SimpleDateFormat FMT        = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    private static final String           SIMPLENAME = DagOnSteroids.class.getSimpleName();
    
    ThrowingBiFunction, Node, NodeCleaner.NodeCleaningResult, NodeCleaner.NodeCleanerException> cleaningFunction = (b, n) -> NodeCleaner.NodeCleaningResult.undefined(b.getKey());

    final private Dag                         dag;
    final private Map         properties;
    final private EnumMap      nameTemplates;
    final private EnumMap  dagCleaners;
    final public  Bumper                      bumper;
    
    private Optional subDagId;

    public DagOnSteroids(final Dag dag, Map nodeAliases, Map properties) {
        this.dag           = dag;
        this.dagCleaners   = new EnumMap(Scope.class);
        this.nameTemplates = new EnumMap(Scope.class);
        
        this.properties = properties != null ? properties : Collections.emptyMap();

        for (Scope scope : Scope.values()) {
            this.dagCleaners.put(scope, new DagCleaner(new BusImpl(this, scope)));
            this.nameTemplates.put(scope, "{{node.name}}-"+scope.toString());
        }
        
        Map nameTemplatesFromProperties = parseMap(this.properties.get("nameTemplates"));
        for (Map.Entry e : nameTemplatesFromProperties.entrySet()) {
            this.nameTemplates.put(Scope.valueOf(e.getKey()), e.getValue());
        }

        if (nodeAliases != null) {
            for (Node node : dag.getNodes()) {
                node.setAlias(nodeAliases.get(node.getName()));
            }
        }

        this.bumper = new Bumper(this.properties.get("bumps"));
        
        this.subDagId = Optional.ofNullable(null);

    }

    public Dag getDag() {
        return this.dag;
    }

    public Map getProperties() {
        return this.properties;
    }

    public EnumMap getNameTemplates() {
        return nameTemplates;
    }    

    public String getSubDagId() {
        return subDagId.orElse(null);
    }

    public void setSubDagId(String subDagId) {
        if (subDagId != null && dag.getSubDag(subDagId) != null) {
            this.subDagId = Optional.ofNullable(subDagId);
        } else {
            this.subDagId = Optional.ofNullable(null);
        }
    }

    public List>> getBusEntries() {
        return this.dagCleaners.entrySet().stream().map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), e.getValue().getBus())).collect(Collectors.toList());
    }

    public EnumMap getDagCleaners() {
        return this.dagCleaners;
    }
    
    public DagCleaner getDagCleaner(Scope scope) {
        return this.dagCleaners.get(scope);
    }
    
    public ThrowingBiFunction, Node, NodeCleaner.NodeCleaningResult, NodeCleaner.NodeCleanerException> getCleaningFunction() {
        return this.cleaningFunction;
    }

    public void setCleaningFunction(ThrowingBiFunction, Node, NodeCleaner.NodeCleaningResult, NodeCleaner.NodeCleanerException> cleaningFunction) {
        this.cleaningFunction = cleaningFunction;
    }
    
    public NodeCleaningResult clean(Scope scope, Node node, Boolean skipRelease) throws NodeCleanerException {
        if (cleaningFunction == null) {
            throw new NodeCleanerException("no cleaningFunction defined in enclosing dag");
        }
        return cleaningFunction.apply(new AbstractMap.SimpleImmutableEntry<>(scope, skipRelease), node);
    }

    /**
     * actually, this enum should be embedded in DagCleaner class, 
     * as it is meaningful only within DagCleaner context, 
     * but java des not allow enum in non-static classes
     */
    public enum State {
        RUNNING, DONE, CANCELLED;
    }
    
    final public class DagCleaner {

        final private Bus       bus;
        final private Map nodeCleanerMap;

        private Journal         journal;
        private Subscription    subscription;
        private String          sub;
        
        final private Set nodesToBeSmudgedSkipRelease;

        public DagCleaner(Bus bus) {
            this.bus = bus;

            if (bus.getScope().equals(Scope.RELEASE)) {
                nodesToBeSmudgedSkipRelease = new HashSet<>();
            } else {
                nodesToBeSmudgedSkipRelease = Collections.emptySet();
            }
            
            this.nodeCleanerMap = new HashMap<>();
            for (Node node : dag.getNodes()) {
                nodeCleanerMap.put(node, new NodeCleaner(this.bus.getScope(), node, NodeState.UNKNOWN));
            }
        }

        public Bus getBus() {
            return this.bus;
        }

        public String getSub() {
            return sub;
        }

        public NodeCleaner getNodeCleaner(Node node) {
            return nodeCleanerMap.get(node);
        }

        public void cleanAll() {
            cleanSubDag(null);
        }

        public void cleanSubDag(String subDagKey) {
            if (getCleaningFunction() == null) {
                throw new RuntimeException("No cleaner defined. Check configuration.");
            }
            if (this.journal == null) {
                this.journal = new Journal();
            } else {
                if (this.journal.state.equals(State.RUNNING)) {
                    throw new RuntimeException("Cleaning already running: Wait for completion, or cancel it and retry.");
                }
            }
            
            this.sub = subDagKey;
            
            final Set nodesToInclude = dag.getNodes(subDagKey) // return all nodes if subDagKey is null
                                                  .stream()
                                                  .filter(n -> !n.getClazz().equals("cluster"))
                                                  .map(n -> n.getName())
                                                  .collect(Collectors.toSet()); 
            synchronized (journal) {
                this.journal.raz();
                
                safeWrite(this.journal, "start first round");

                final Set nodesToBeSmudged      = new HashSet<>();
                final Set nodesToBeMagicCleaned = new HashSet<>();
                
                {
                    if (bus.getScope().equals(Scope.RELEASE)) {
                        nodesToBeSmudgedSkipRelease.clear();
                    }
                    
                    for (Node node : dag.getTopologicalNodes()) {
                        if ( node.getValue()             != null    && 
                             node.getValue().getGucrid() != null    && 
                             !node.getValue().getGucrid().contains("SNAPSHOT")) {
                            // force CLEAN state if not a snapshot
                            nodesToBeMagicCleaned.add(node);
                            explainMagicClean(node, "not a snapshot", node.getValue().getGucrid().toString());
                        } else if (!nodesToInclude.contains(node.getName())){
                            // force CLEAN state if node is not included (e.g. sub dags)
                            nodesToBeMagicCleaned.add(node);
                            explainMagicClean(node, "not in sub dag", nodesToInclude.toString());
                        } else {
                            // SMUDGE everything else
                            nodesToBeSmudged.add(node);
                        }
                    }

                    {
                        // in RELEASE scope,
                        // every node  
                        //    1. that is *not* in the sub graph (i.e. magic-cleaned)
                        //    2. which has at least one smudged node as predecessor
                        //    must be smudged and the clean operation flagged with "skipRelease"
                        if (bus.getScope().equals(Scope.RELEASE)) {
                            for (Node smudged : nodesToBeSmudged) {
                                Iterator bfi = dag.getBreadthFirstIterator(smudged);
                                while (bfi.hasNext()){
                                    Node succ = bfi.next();
                                    if (nodesToBeMagicCleaned.contains(succ)) {
                                        nodesToBeMagicCleaned.remove(succ);
                                        nodesToBeSmudgedSkipRelease.add(succ);
                                    }
                                }
                            }
                        }
                    }
                }
                
                bus.send(MAGIC_CLEAN,   nodesToBeMagicCleaned,          SIMPLENAME);
                bus.send(SMUDGE,        nodesToBeSmudged,               SIMPLENAME);
                bus.send(SMUDGE,        nodesToBeSmudgedSkipRelease,    SIMPLENAME);
                for (Node node : dag.getTopologicalNodes()) {
                    safeWrite(this.journal, String.format("set node /%s/ state to %s",
                            node.getName(),
                            getNodeCleaner(node).getState()));
                }

                bus.toObservable().filter( event -> event.getType().equals(CLEAN_STARTED)  || 
                                                    event.getType().equals(CLEAN_OK)       || 
                                                    event.getType().equals(CLEAN_ERROR)    || 
                                                    event.getType().equals(CLEAN_ABORTED)).subscribe(event -> {
                    Node node = event.get();
                    
                    safeWrite(this.journal, String.format("received event %s on node /%s/ from [ %s ]",
                            event.getType(),
                            node.getName(),
                            event.getSource()
                    ));
                    if (journal != null && !journal.state.equals(State.RUNNING)) {
                        safeWrite(this.journal, String.format("Cleaning is not running (state=%s), doing nothing.", journal.state.toString()));
                        return;
                    }
                    
                    if (event.getType().equals(CLEAN_OK)) {
                        queue();
                    } else if (event.getType().equals(CLEAN_ERROR) || 
                               event.getType().equals(CLEAN_ABORTED)) {
                        error(event.get());
                    }
                    ifAllNodesAreCleanThenDone();
                });

                queue();
            }
        }

        private void explainMagicClean(Node node, String reason, String optional) {
            safeWrite(this.journal, String.format("set node /%s/ state to %s: %s%s",                     
                          node.getName(),                   
                          CLEAN,
                          reason,                           
                          optional != null ? (" - " + optional) : ""
            ));
        }

        public String getJournalAsString() {
            if (journal != null) {
                return journal.toString();
            } else {
                return "nothing to read";
            }
        }

        public void done() {
            try {
                clean(this.bus.getScope(), null, null);
                safeWrite(this.journal, "ordered closing of cleaning process");
            } catch (NodeCleanerException e) {
                safeWrite(this.journal, String.format("exception while ordering closing of cleaning process in scope &s : %s", this.bus.getScope(), e.getMessage()));
                throw new RuntimeException(e);
            }

            if (subscription != null) {
                subscription.cancel();
                subscription = null;
            }
            if (journal != null) {
                journal.done();
            }
        }

        public void error(Node error) {
            if (subscription != null) {
                subscription.cancel();
                subscription = null;
            }
            if (journal != null) {
                journal.error(error);
                journal.close();
            }
        }

        public void cancel() {
            if (subscription != null) {
                subscription.cancel();
                subscription = null;
            }
            if (journal != null) {
                journal.cancel();
            }
        }

        private void ifAllNodesAreCleanThenDone() {
            if (journal == null) {
                return;
            }
            if (!journal.state.equals(State.RUNNING)) {
                return;
            }
            int doneCount = 0;
            final String subDag = bus.getScope().equals(Scope.RELEASE) ? null : getSubDagId(); // get all nodes for RELEASE scope
            final Collection nodesSubSet = dag.getNodes(subDag);
            for (Node node : nodesSubSet) {
                NodeState state = getNodeCleaner(node).getState();
                if (state != null) {
                    if (state.oneOf(CLEAN, CLEANED)) {
                        doneCount++;
                    }
                }
            }
            if (doneCount == nodesSubSet.size()) {
                done();
            }
        }

        private void orderCleaningOfNode(Node node, Boolean skipRelease) {

            NodeCleaner.NodeCleaningResult result;
            try {
                result = clean(bus.getScope(), node, skipRelease);
            } catch (Exception e) {
                URI source = null;
                if (e instanceof NodeCleanerException) {
                    source = ((NodeCleanerException)e).getSource();
                }
                LOG.error("ordering cleaning of node /{}/ failed with exception \"{}\". (caught exception is not rethrown but cleanNode function will return not zero + exception message). source={}", node.getName(), e.getMessage(), source);
                result = NodeCleaner.NodeCleaningResult.fromException(bus.getScope(), e, source);
            }

            if (result != null && result.asBoolean()) {
                bus.send(CLEAN_ORDER_OK, Collections.singletonList(node), SIMPLENAME);
                safeWrite(this.journal, String.format("ordered cleaning of node /%s/ (%s)", 
                        node.getName(), 
                        result.getURI()));
            } else {
                String error = (result == null ? "null result" : result.getStatus() + " " + result.getReason());
                bus.send(CLEAN_ORDER_ERROR, Collections.singletonList(node), error);
                safeWrite(this.journal, String.format("cannot order cleaning of node /%s/: %s [%s]",
                        node.getName(),
                        error,
                        result.getURI()
                ));            
            }
        }

        private void queue() {
            safeWrite(this.journal, "start inpecting queue");

            for (Node node : dag.getTopologicalNodes()) {
                final NodeState thisState = getNodeCleaner(node).getState();
                // check only dirty nodes
                if (!thisState.oneOf(DIRTY)) {
                    continue;
                }
                // inspect predecessors
                final Collection predecessors = dag.predecessorsOf(node);
                int doneCount = 0;
                int errorCount = 0;
                for (final Node pred : predecessors) {
                    final NodeState predState = getNodeCleaner(pred).getState();
                    if (predState != null) {
                        if (predState.oneOf(CLEAN, CLEANED)) {
                            doneCount++;
                        }
                    }
                }
                // all predecessors are clean
                if (doneCount == predecessors.size()) {
                    if (predecessors.size() == 0) {
                        safeWrite(this.journal, String.format("node /%s/ is %s and has no predecessor : time to clean it, isn't it?",
                                node.getName(),
                                thisState
                        ));
                    } else {
                        safeWrite(this.journal, String.format("node /%s/ is %s and all its predecessors (%d) are DONE (with %d error(s)) : time to clean it, isn't it?",
                                node.getName(),
                                thisState,
                                doneCount,
                                errorCount
                        ));
                    }
                    orderCleaningOfNode(node, nodesToBeSmudgedSkipRelease.contains(node));
                }
            }
            safeWrite(this.journal, "done inpecting queue");
        }

        class Journal implements Closeable {

            private State                 state;
            private ByteArrayOutputStream baos;
            private BufferedWriter        writer;

            Journal() {
                raz();
            }

            void raz() {
                bus.send(INITIALIZE, Collections.emptyList(), SIMPLENAME);
                this.state   = State.RUNNING;
                this.baos    = new ByteArrayOutputStream();
                this.writer  = new BufferedWriter(new OutputStreamWriter(baos, StandardCharsets.UTF_8));
            }

            void write(String entry) {
                try {
                    writer.newLine();
                    writer.write(FMT.format(new Date()));
                    writer.write(" [");
                    writer.write(Thread.currentThread().getName());
                    writer.write("] ");
                    writer.write(entry);
                    writer.flush();
                } catch (IOException e) {
                    LOG.warn("ignored exception {}", e.getMessage());
                }
            }

            void done() {
                write("done!");
                this.state = State.DONE;
                close();
            }

            void cancel() {
                write("cancelled by external action");
                this.state = State.CANCELLED;
                close();
            }

            void error(Node node) {
                write("node [ " + node.getName() + " ]");
            }

            @Override
            public void close() {
                for (Node node : dag.getTopologicalNodes()) {
                    write("node /" + node.getName() + "/ state is " + getNodeCleaner(node).getState().toString());
                }
                bus.send(TERMINATE, Collections.emptyList(), SIMPLENAME);
            }

            @Override
            public String toString() {
                return this.state.toString() + "\n"+ new String(baos.toByteArray(), StandardCharsets.UTF_8);
            }
        }

        public State getState() {
            if (this.journal == null) {
                return null;
            }
            return this.journal.state;
        }

    }

    static void safeWrite(Journal journal, String entry) {
        LOG.debug(entry);
        if (journal == null) {
            return;
        }
        journal.write(entry);
    }
    
    static public List getNodesFromNameAndBranchContains(final Dag dag, final String nodeName, final String branchSubstring) {
        final List ret = new LinkedList<>();
        
        for (Node node : dag.getNodesFromName(nodeName)) {
            if (!containsBranchSubstring(node, branchSubstring)) {
                continue;
            }
            ret.add(node);
        }

        LOG.debug("found {} nodes with nodeName='{}' and branch='{}' in dag '{}'", ret.size(), nodeName, branchSubstring, dag.getName());

        return ret;
    }

    static private boolean containsBranchSubstring(final Node node, final String branchSubstring) {
        final String nodeBranch;
        
        if (node                        != null &&
            node.getValue()             != null &&
            node.getValue().getBranch() != null) {
            nodeBranch = node.getValue().getBranch(); 
        } else {
            nodeBranch = null;
        }
        
        if (nodeBranch == null && branchSubstring == null) {
            return true;
        }
        
        if (nodeBranch == null && branchSubstring == null) {
            return false;
        }
        
        return nodeBranch.contains(branchSubstring);
    }

    static final private Pattern DAGNAMECONTAINSSUB = Pattern.compile("(.*)%2Fsubs%2(.*)");
    static public String[] parseName(String dagName) {
        // e.g. development.neo.ondemand.com%2Fsubs%2F124966568
        // to test regexps : http://www.regexplanet.com/advanced/java/index.html
        Matcher dagNameContainsSub = DAGNAMECONTAINSSUB.matcher(dagName);
        if (dagNameContainsSub.matches()) {
            return new String[] {dagNameContainsSub.group(1), dagNameContainsSub.group(2)};
        } else {
            return new String[] {dagName, null};
        }
    }

    @JsonIgnoreProperties(ignoreUnknown=true)
    public static class NodeCleaner {
        
        @JsonProperty
        private UUID         id;

        @JsonProperty
        final private Node   node;
        @JsonProperty
        final private Scope  scope;
        
        @JsonProperty
        private NodeState    state;
        
        @JsonIgnore
        private Long         start;
        @JsonIgnore
        private Long         stop;
        
        @JsonCreator
        public NodeCleaner(
                @JsonProperty(value="scope") final Scope scope,
                @JsonProperty(value="node")  final Node  node,
                @JsonProperty(value="state")  final NodeState state) {
            this.node  = node;
            this.scope = scope;
            this.state = state;
        }

        public String getId() {
            return this.id == null ? null : this.id.toString();
        }

        public void setId(String id) {
            if (id == null) {
                this.id = null;
                if (this.start != null) {
                    this.stop = new Date().getTime();
                }
            } else {
                this.start = new Date().getTime();
                this.stop  = null;
                if (id.matches("[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")) {
                    this.id = UUID.fromString(id);
                }
            }
        }
        
        @JsonIgnore
        public Node getNode() {
            return node;
        }

        public NodeState getState() {
            return state;
        }

        public void setState(NodeState state) {
            this.state = state;
        }
        
        public Long getDuration() {
            if (start == null) {
                return null;
            } else if (stop == null) {
                return new Date().getTime() - start;
            }
            return stop - start;
        }
        
        public enum NodeState {
            // at start
            @JsonProperty("UNKNOWN")            UNKNOWN, 
            @JsonProperty("CLEAN")              CLEAN, 
            @JsonProperty("DIRTY")              DIRTY, 
            // doing something 
            @JsonProperty("CLEANING_ORDERED")   CLEANING_ORDERED,
            @JsonProperty("BEING_CLEANED")      BEING_CLEANED,
            // at end
            @JsonProperty("UNCLEANABLE")        UNCLEANABLE,
            @JsonProperty("CLEANED")            CLEANED,
            @JsonProperty("FAIL")               FAIL,
            @JsonProperty("ABORTED")            ABORTED;
            
            public boolean oneOf(NodeState ... others) {
                for (NodeState other : others) {
                    if (this.equals(other)) {
                        return true;
                    }
                }
                return false;
            }
        }

        public static class NodeCleanerException extends Exception {
        
            private static final long serialVersionUID = -3468108554682441131L;
            
            final public URI source;
        
            public NodeCleanerException() {
                this((URI)null);
            }
        
            public NodeCleanerException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
                this((URI)null, arg0, arg1, arg2, arg3);
            }
        
            public NodeCleanerException(String arg0, Throwable arg1) {
                this((URI)null, arg0, arg1);
            }
        
            public NodeCleanerException(String arg0) {
                this((URI)null, arg0);
            }
        
            public NodeCleanerException(Throwable arg0) {
                this((URI)null, arg0);
            }
        
            public NodeCleanerException(URI source, String arg0, Throwable arg1, boolean arg2, boolean arg3) {
                super(addSourceToMessage(arg0, source), arg1, arg2, arg3);
                this.source = source;
            }
        
            public NodeCleanerException(URI source, String arg0, Throwable arg1) {
                super(addSourceToMessage(arg0, source), arg1);
                this.source = source;
            }
        
            public NodeCleanerException(URI source, String arg0) {
                super(addSourceToMessage(arg0, source));
                this.source = source;
            }
        
            public NodeCleanerException(URI source, Throwable arg0) {
                super(sourceAsMessage(source), arg0);
                this.source = source;
            }
        
            public NodeCleanerException(URI source) {
                super(sourceAsMessage(source));
                this.source = source;
            }
            
            public URI getSource() {
                return source;
            }
            
            static private final String addSourceToMessage(String message, URI source) {
                return message + (source!=null ? " - source: " + source.toString() : "");
            }
            
            static private final String sourceAsMessage(URI source) {
                return (source!=null ? "source: " + source.toString() : "");
            }
        
        }

        @JsonIgnoreProperties(ignoreUnknown=true)
        public static class NodeCleaningResult {
            private final Scope  scope;
            private final int    status;
            private final String reason;
            private final URI    uri;
            
            @JsonCreator
            public NodeCleaningResult(
                    @JsonProperty(value="bus")    Scope  scope, 
                    @JsonProperty(value="status") int    status, 
                    @JsonProperty(value="reason") String reason, 
                    @JsonProperty(value="uri")    URI    uri) {
                this.scope  = scope;
                this.status = status;
                this.reason = reason;
                this.uri    = uri;
            }
            public Scope getScope() {
                return scope;
            }
            public int getStatus() {
                return status;
            }
            public String getReason() {
                return reason;
            }
            public URI getURI() {
                return uri;
            }
            @JsonIgnore
            public boolean asBoolean() {
                return (this.status == 0 || this.status/100 == 2);
            }
            
            static public NodeCleaningResult undefined(Scope c) {
                return new NodeCleaningResult(c, -1, "undefined", null);
            }
            public static NodeCleaningResult fromException(Scope c, Exception e, URI uri) {
                String msg = e.getClass().getSimpleName() + "| " + e.getMessage();
                if (e.getCause() != null) {
                    msg = msg + "\n(cause:" +e.getCause().getClass().getSimpleName()+"| "+e.getCause().getMessage() + ")"; 
                }
                return new NodeCleaningResult(c, -1, msg, uri);
            }
            public static NodeCleaningResult ok(Scope c, URI uri) {
                return new NodeCleaningResult(c, 0, "", uri);
            }
            public static NodeCleaningResult from(Scope c, int status, String reason, URI uri) {
                return new NodeCleaningResult(c, status, reason, uri);
            }
        }

    }
    
    @SuppressWarnings("unused")
    private static String formatMap(Map map) {
        if (map == null || map.size() == 0) {
            return "";
        }
        return Joiner.on(",").withKeyValueSeparator("=").join(map);
    }

   private static Map parseMap(String formattedMap) {
        if (formattedMap == null || formattedMap.length() == 0) {
            return Collections.emptyMap();
        }
        return Splitter.on(",").withKeyValueSeparator("=").split(formattedMap);
    }

   @JsonIgnoreProperties
   static public class NodeNameVersion {
       @JsonProperty
       final public String name;
       @JsonProperty
       final public String version;

       static public NodeNameVersion create(final String name, final String version) {
           return new NodeNameVersion(name, version);
       }

       public NodeNameVersion(final String name, final String version) {
           this.name    = name;
           this.version = version;
       }
   }

   public NodeNameVersion getCurrent(Node node, boolean range) {
       if (node != null &&
           node.getName() != null &&
           !node.getName().trim().isEmpty() &&
           node.getValue() != null ) {
           String gucridAsString = node.getValue().getGucrid();
           if (gucridAsString == null || gucridAsString.trim().isEmpty()) {
               return null;
           }
           final Gucrid gucrid = Gucrid.create(gucridAsString);
           if (gucrid == null || gucrid.getVersion() == null|| !gucrid.getVersion().isPresent()) {
               return null;
           }
           String v = gucrid.getVersion().get().toString();
           if (range) {
               v = "[,"+v+"]";
           }
           return NodeNameVersion.create(node.getName(), v);
       }
       return null;
   }
   
   public Collection getCurrents(boolean range) {
       List currentNodeNameVersions = new ArrayList<>();
       for (Node node : getDag().getNodes()) {
           NodeNameVersion nodeNameVersion = getCurrent(node, range);
           if (nodeNameVersion != null) {
               currentNodeNameVersions.add(nodeNameVersion);
           }
       }
       return currentNodeNameVersions;
   }
   
   public NodeNameVersion getNext(Node node, boolean range, boolean allowSnapshots, boolean forbidSnapshotFromRelease) {
       if (node != null &&
           node.getName() != null &&
           !node.getName().trim().isEmpty() &&
           node.getValue() != null ) {
           String gucridAsString = node.getValue().getGucrid();
           if (gucridAsString == null || gucridAsString.trim().isEmpty()) {
               return null;
           }
           final Gucrid gucrid = Gucrid.create(gucridAsString);
           if (gucrid == null || gucrid.getSemanticVersion() == null|| !gucrid.getSemanticVersion().isPresent()) {
               return null;
           }
           Bump bump = bumper.fromNodeName(node.getName());
           if (bump == null) {
               return null;
           }
           Version releaseVersion = gucrid.getReleaseVersion();
           if (releaseVersion == null) {
               return null;
           }
           Version nextDevelopmentVersion = gucrid.getNextDevelopmentVersion(bump);
           if (nextDevelopmentVersion == null) {
               return null;
           }
           String v;
           if (!allowSnapshots || (forbidSnapshotFromRelease && !gucridAsString.contains("-SNAPSHOT")) ) {
               v = releaseVersion.toString();
           } else {
               v = nextDevelopmentVersion.toString();
           }
           if (range) {
               v = "[,"+v+"]";
           }
           return NodeNameVersion.create(node.getName(), v);
       }
       return null;
   }
   
   public Collection getNexts(boolean range, boolean allowSnapshots) {
       List nextNodeNameVersions = new ArrayList<>();
       for (Node node : getDag().getNodes()) {
           final NodeNameVersion nodeNameVersion = getNext(node, range, allowSnapshots, true);
           if (nodeNameVersion != null) {
               nextNodeNameVersions.add(nodeNameVersion);
           }
       }
       return nextNodeNameVersions;
   }
   
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy