xbdd.webapp.resource.feature.Feature.java Source code

Java tutorial

Introduction

Here is the source code for xbdd.webapp.resource.feature.Feature.java

Source

/**
 * Copyright (C) 2015 Orion Health (Orchestral Development Ltd)
 *
 * 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 xbdd.webapp.resource.feature;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;

import xbdd.util.StatusHelper;
import xbdd.webapp.factory.MongoDBAccessor;
import xbdd.webapp.util.Coordinates;
import xbdd.webapp.util.Field;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;

@Path("/rest/feature")
public class Feature {

    private final MongoDBAccessor client;
    private static int MAX_ENVIRONMENTS_FOR_A_PRODUCT = 10;

    @Inject
    public Feature(final MongoDBAccessor client) {
        this.client = client;
    }

    @SuppressWarnings("unchecked")
    /**
     * Uses the '.+' regexp on featureId to allow for symbols such as slashes in the id
     *
     * @param String featureId The featureId to get the history for
     * @return DBObjet Returns the past feature status for the given featureId
     */
    @GET
    @Path("/rollup/{product}/{major}.{minor}.{servicePack}/{featureId:.+}")
    public DBObject getFeatureRollup(@BeanParam final Coordinates coordinates,
            @PathParam("featureId") final String featureId) {
        final List<BasicDBObject> features = new ArrayList<BasicDBObject>();
        final DB db = this.client.getDB("bdd");
        final DBCollection collection = db.getCollection("features");
        final DBCollection summary = db.getCollection("summary");
        final BasicDBObject example = coordinates.getRollupQueryObject(featureId);
        final DBCursor cursor = collection.find(example,
                new BasicDBObject("id", 1).append("coordinates.build", 1).append("calculatedStatus", 1)
                        .append("originalAutomatedStatus", 1).append("statusLastEditedBy", 1));
        try {
            while (cursor.hasNext()) {
                final DBObject doc = cursor.next();
                final BasicDBObject rollup = new BasicDBObject()
                        .append("build", ((DBObject) doc.get("coordinates")).get("build"))
                        .append("calculatedStatus", doc.get("calculatedStatus"))
                        .append("originalAutomatedStatus", doc.get("originalAutomatedStatus"))
                        .append("statusLastEditedBy", doc.get("statusLastEditedBy"));
                features.add(rollup);
            }
        } finally {
            cursor.close();
        }
        final BasicDBObject returns = new BasicDBObject().append("coordinates", coordinates.getRollupCoordinates()
                .append("featureId", featureId).append("version", coordinates.getVersionString()));

        final DBObject buildOrder = summary.findOne(coordinates.getQueryObject());
        final List<String> buildArray = (List<String>) buildOrder.get("builds");
        final List<BasicDBObject> orderedFeatures = new ArrayList<BasicDBObject>();

        for (String build : buildArray) {
            for (BasicDBObject feature : features) {
                if (feature.get("build").equals(build)) {
                    orderedFeatures.add(feature);
                    break;
                }
            }
        }

        returns.append("rollup", orderedFeatures);

        return returns;
    }

    /**
     * Uses the '.+' regexp on featureId to allow for symbols such as slashes in the id
     *
     * @param String featureId The featureId to get the history for
     * @return DBObjet Returns the the current features state and details (environments, tips, steps and scenarios)
     */
    @GET
    @Path("/{product}/{major}.{minor}.{servicePack}/{build}/{featureId:.+}")
    public DBObject getFeature(@BeanParam final Coordinates coordinates,
            @PathParam("featureId") final String featureId) {
        final DB db = this.client.getDB("bdd");
        final DBCollection tips = db.getCollection("features");
        final BasicDBObject example = new BasicDBObject().append("id", featureId).append("coordinates",
                coordinates.getReportCoordinates());
        final DBObject feature = tips.findOne(example);
        if (feature != null) {
            Feature.embedTestingTips(feature, coordinates, db);
        }
        return feature;
    }

    @SuppressWarnings("unchecked")
    protected void updateTestingTips(final DB db, final Coordinates coordinates, final String featureId,
            final DBObject feature) {
        final DBCollection tips = db.getCollection("testingTips");
        final List<DBObject> elements = (List<DBObject>) feature.get("elements");
        for (final DBObject scenario : elements) {
            if (scenario.get("testing-tips") != null) {
                final String tipText = (String) scenario.get("testing-tips");
                final String scenarioId = (String) scenario.get("id");
                final BasicDBObject tipQuery = coordinates.getTestingTipsCoordinatesQueryObject(featureId,
                        scenarioId);
                DBObject oldTip = null;
                // get the most recent tip that is LTE to the current coordinates. i.e. sort in reverse chronological order and take the
                // first item (if one exists).
                final DBCursor oldTipCursor = tips.find(tipQuery)
                        .sort(new BasicDBObject("coordinates.major", -1).append("coordinates.minor", -1)
                                .append("coordinates.servicePack", -1).append("coordinates.build", -1))
                        .limit(1);
                try {
                    if (oldTipCursor.hasNext()) {
                        oldTip = oldTipCursor.next();
                    }
                } finally {
                    oldTipCursor.close();
                }
                if (oldTip != null) { // if there is an old tip...
                    final String oldTipText = (String) oldTip.get("testing-tips"); // get it and...
                    if (!tipText.equals(oldTipText)) {// compare it to the current tip to it, if they're not the same...
                        final DBObject newTip = new BasicDBObject("testing-tips", tipText)
                                .append("coordinates", coordinates.getTestingTipsCoordinates(featureId, scenarioId))
                                .append("_id", coordinates.getTestingTipsId(featureId, scenarioId));
                        tips.save(newTip);// then save this as a new tip.
                    }
                } else { // no prior tip exists, add this one.
                    final DBObject newTip = new BasicDBObject("testing-tips", tipText)
                            .append("coordinates", coordinates.getTestingTipsCoordinates(featureId, scenarioId))
                            .append("_id", coordinates.getTestingTipsId(featureId, scenarioId));
                    tips.save(newTip);// then save this as a new tip.
                }
            }
            scenario.removeField("testing-tips");
        }
    }

    /**
     * Uses the '.+' regexp on featureId to allow for symbols such as slashes in the id
     *
     * @param String featureId The featureId to make changes to
     * @return DBObjet Returns the the features new state if changes were made and returns null if bad JSON was sent
     */
    @PUT
    @Path("/{product}/{major}.{minor}.{servicePack}/{build}/{featureId:.+}")
    @Consumes("application/json")
    public DBObject putFeature(@BeanParam final Coordinates coordinates,
            @PathParam("featureId") final String featureId, @Context final HttpServletRequest req,
            final DBObject feature) {
        feature.put("calculatedStatus", StatusHelper.getFeatureStatus(feature));
        try {
            final DB db = this.client.getDB("bdd");
            final DBCollection collection = db.getCollection("features");
            final BasicDBObject example = coordinates.getReportCoordinatesQueryObject().append("id", featureId);
            final DBObject report = collection.findOne(example);

            // get the differences/new edits

            // Detect if the edits caused a change
            feature.put("statusLastEditedBy", req.getRemoteUser());
            feature.put("lastEditOn", new Date());
            final BasicDBList edits = updateEdits(feature, report);
            feature.put("edits", edits);

            updateTestingTips(db, coordinates, featureId, feature); // save testing tips / strip them out of the document.
            updateEnvironmentDetails(db, coordinates, feature);
            collection.save(feature);
            Feature.embedTestingTips(feature, coordinates, db); // rembed testing tips.
            return feature;// pull back feature - will re-include tips that were extracted prior to saving
        } catch (final Throwable th) {
            th.printStackTrace();
            return null;
        }
    }

    /**
     * Goes through each environment detail on this feature and pushes each unique one to a per-product document in the 'environments'
     * collection.
     *
     * @param db
     * @param coordinates
     * @param feature
     */
    @SuppressWarnings("unchecked")
    public void updateEnvironmentDetails(final DB db, final Coordinates coordinates, final DBObject feature) {
        final DBCollection env = db.getCollection("environments");
        final List<DBObject> elements = (List<DBObject>) feature.get("elements");
        final BasicDBObject envQuery = coordinates.getQueryObject(Field.PRODUCT);
        // pull back the "product" document containing all the environments.
        DBObject productEnvironments = env.findOne(envQuery);
        // if one doesn't exist then create it.
        if (productEnvironments == null) {
            productEnvironments = new BasicDBObject();
            productEnvironments.put("coordinates", coordinates.getObject(Field.PRODUCT));
        }
        // pull back the list of environments
        List<Object> envs = (List<Object>) productEnvironments.get("environments");
        // if the list doesn't exist then create it.
        if (envs == null) {
            envs = new BasicDBList();
            productEnvironments.put("environments", envs);
        }
        final List<String> titleCache = new ArrayList<String>();
        // go through each scenario, pull out the environment details and add them to the back of the list.
        for (final DBObject scenario : elements) {
            String notes = (String) scenario.get("environment-notes");
            if (notes != null) {
                notes = notes.trim();
                if (notes.length() > 0) {
                    if (!titleCache.contains(notes)) {
                        titleCache.add(notes);
                    }
                }
            }
        }
        // go through each unique environment detail, remove it if it is already in the list and append to the end.
        for (final String environmentDetail : titleCache) {
            envs.remove(environmentDetail);
            envs.add(environmentDetail);
        }
        // if the list gets too long, truncate it on a LRU basis.
        if (envs.size() > MAX_ENVIRONMENTS_FOR_A_PRODUCT) {
            envs = envs.subList(envs.size() - MAX_ENVIRONMENTS_FOR_A_PRODUCT, envs.size());
            productEnvironments.put("environments", envs);
        }
        // save the list back.
        env.save(productEnvironments);
    }

    @SuppressWarnings("unchecked")
    public static void embedTestingTips(final DBObject feature, final Coordinates coordinates, final DB db) {
        final DBCollection tips = db.getCollection("testingTips");
        final List<DBObject> elements = (List<DBObject>) feature.get("elements");
        for (final DBObject scenario : elements) {
            DBObject oldTip = null;
            final BasicDBObject tipQuery = coordinates
                    .getTestingTipsCoordinatesQueryObject((String) feature.get("id"), (String) scenario.get("id"));
            // get the most recent tip that is LTE to the current coordinates. i.e. sort in reverse chronological order and take the first
            // item (if one exists).
            final DBCursor oldTipCursor = tips.find(tipQuery)
                    .sort(new BasicDBObject("coordinates.major", -1).append("coordinates.minor", -1)
                            .append("coordinates.servicePack", -1).append("coordinates.build", -1))
                    .limit(1);
            try {
                if (oldTipCursor.hasNext()) {
                    oldTip = oldTipCursor.next();
                    scenario.put("testing-tips", oldTip.get("testing-tips"));
                }
            } finally {
                oldTipCursor.close();
            }
        }
    }

    private BasicDBList updateEdits(final DBObject feature, final DBObject previousVersion) {
        BasicDBList edits = (BasicDBList) feature.get("edits");
        if (edits == null) {
            edits = new BasicDBList();
        }
        final BasicDBList newEdits = new BasicDBList();
        final BasicDBObject edit = new BasicDBObject().append("name", feature.get("statusLastEditedBy"))
                .append("date", feature.get("lastEditOn")).append("prev", previousVersion.get("calculatedStatus"))
                .append("curr", feature.get("calculatedStatus"))
                .append("stepChanges", constructEditStepChanges(feature, previousVersion));
        newEdits.add(edit);
        newEdits.addAll(edits);
        return newEdits;
    }

    private BasicDBList constructEditStepChanges(final DBObject currentVersion, final DBObject previousVersion) {
        final BasicDBList stepChanges = new BasicDBList();
        final BasicDBList elements = (BasicDBList) currentVersion.get("elements");
        final BasicDBList prevElements = (BasicDBList) previousVersion.get("elements");
        if (elements != null) {
            for (int i = 0; i < elements.size(); i++) {
                final BasicDBList allSteps = new BasicDBList();
                final BasicDBList changes = new BasicDBList();
                final BasicDBObject element = (BasicDBObject) elements.get(i);
                final BasicDBObject prevElement = (BasicDBObject) prevElements.get(i);
                final String scenarioName = (String) element.get("name");
                boolean currManual = false;
                boolean prevManual = false;

                // get all scenario steps
                if ((BasicDBObject) element.get("background") != null) {
                    for (int j = 0; j < ((BasicDBList) ((BasicDBObject) element.get("background")).get("steps"))
                            .size(); j++) {
                        final BasicDBObject step = (BasicDBObject) ((BasicDBList) ((BasicDBObject) element
                                .get("background")).get("steps")).get(j);
                        final BasicDBObject prevStep = (BasicDBObject) ((BasicDBList) ((BasicDBObject) prevElement
                                .get("background")).get("steps")).get(j);
                        final String id = (String) step.get("keyword") + (String) step.get("name");
                        if (((BasicDBObject) step.get("result")).get("manualStatus") != null) {
                            currManual = true;
                        }
                        if (((BasicDBObject) prevStep.get("result")).get("manualStatus") != null) {
                            prevManual = true;
                        }
                        final BasicDBObject compareStep = new BasicDBObject().append("id", id).append("curr", step)
                                .append("prev", prevStep);
                        allSteps.add(compareStep);
                    }
                }

                if ((BasicDBList) element.get("steps") != null) {
                    for (int j = 0; j < ((BasicDBList) element.get("steps")).size(); j++) {
                        final BasicDBObject step = (BasicDBObject) ((BasicDBList) element.get("steps")).get(j);
                        final BasicDBObject prevStep = (BasicDBObject) ((BasicDBList) prevElement.get("steps"))
                                .get(j);
                        final String id = (String) step.get("keyword") + (String) step.get("name");
                        if (((BasicDBObject) step.get("result")).get("manualStatus") != null) {
                            currManual = true;
                        }
                        if (((BasicDBObject) prevStep.get("result")).get("manualStatus") != null) {
                            prevManual = true;
                        }
                        final BasicDBObject compareStep = new BasicDBObject().append("id", id).append("curr", step)
                                .append("prev", prevStep);
                        allSteps.add(compareStep);
                    }
                }

                for (int j = 0; j < allSteps.size(); j++) {
                    formatStep(changes, (BasicDBObject) allSteps.get(j), currManual, prevManual);
                }

                // only add if changes have been made
                if (changes.size() > 0) {
                    final BasicDBObject singleScenario = new BasicDBObject().append("scenario", scenarioName)
                            .append("changes", changes);
                    stepChanges.add(singleScenario);
                }
            }
        }
        return stepChanges;
    }

    private void formatStep(final BasicDBList changes, final BasicDBObject step, final boolean currManual,
            final boolean prevManual) {
        String currState, currCause, prevState, prevCause;

        final BasicDBObject currStep = ((BasicDBObject) step.get("curr"));
        if (((BasicDBObject) currStep.get("result")).get("manualStatus") != null) {
            currState = (String) ((BasicDBObject) currStep.get("result")).get("manualStatus");
            currCause = "manual";
        } else {
            currCause = "auto";
            if (currManual) {
                currState = "undefined";
            } else {
                currState = (String) ((BasicDBObject) currStep.get("result")).get("status");
            }
        }

        final BasicDBObject prevStep = ((BasicDBObject) step.get("prev"));
        if (((BasicDBObject) prevStep.get("result")).get("manualStatus") != null) {
            prevState = (String) ((BasicDBObject) prevStep.get("result")).get("manualStatus");
            prevCause = "manual";
        } else {
            prevCause = "auto";
            if (prevManual) {
                prevState = "undefined";
            } else {
                prevState = (String) ((BasicDBObject) prevStep.get("result")).get("status");
            }
        }

        // only add if different
        if (!currState.equals(prevState) || !currCause.equals(prevCause)) {
            final BasicDBObject stateChange = new BasicDBObject().append("id", step.get("id"))
                    .append("curr", currState).append("prev", prevState);
            changes.add(stateChange);
        }
    }
}