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

Java tutorial

Introduction

Here is the source code for xbdd.webapp.resource.feature.Report.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.io.IOException;
import java.util.ArrayList;
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.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;

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

import com.mongodb.AggregationOutput;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSInputFile;
import com.mongodb.util.JSON;

@Path("/rest/reports")
public class Report {

    private final MongoDBAccessor client;

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

    Logger log = Logger.getLogger(Report.class);

    @GET
    @Path("/{product}/{major}.{minor}.{servicePack}/{build}")
    @Produces("application/json")
    public DBObject getReportByProductVersionId(@BeanParam final Coordinates coordinates,
            @QueryParam("searchText") final String searchText, @QueryParam("viewPassed") final Integer viewPassed,
            @QueryParam("viewFailed") final Integer viewFailed,
            @QueryParam("viewUndefined") final Integer viewUndefined,
            @QueryParam("viewSkipped") final Integer viewSkipped, @QueryParam("start") final String start,
            @QueryParam("limit") final Integer limit) {

        final BasicDBObject example = QueryBuilder.getInstance().buildFilterQuery(coordinates, searchText,
                viewPassed, viewFailed, viewUndefined, viewSkipped, start);

        final DB db = this.client.getDB("bdd");
        final DBCollection collection = db.getCollection("features");
        final DBCursor cursor = collection.find(example).sort(Coordinates.getFeatureSortingObject());
        try {
            if (limit != null) {
                cursor.limit(limit);
            }
            final BasicDBList featuresToReturn = new BasicDBList();
            while (cursor.hasNext()) {
                featuresToReturn.add(cursor.next());
            }
            embedTestingTips(featuresToReturn, coordinates, db);
            return featuresToReturn;
        } finally {
            cursor.close();
        }
    }

    @SuppressWarnings("unchecked")
    @GET
    @Produces("application/json")
    public DBObject getSummaryOfAllReports(@Context final HttpServletRequest req) {
        final DB db = this.client.getDB("bdd");
        final DBCollection collection = db.getCollection("summary");
        final DBCollection usersCollection = db.getCollection("users");

        final BasicDBObject user = new BasicDBObject();
        user.put("user_id", req.getRemoteUser());

        final DBCursor userCursor = usersCollection.find(user);
        DBObject userFavourites;

        if (userCursor.count() != 1) {
            userFavourites = new BasicDBObject();
        } else {
            final DBObject uDoc = userCursor.next();
            if (uDoc.containsField("favourites")) {
                userFavourites = (DBObject) uDoc.get("favourites");
            } else {
                userFavourites = new BasicDBObject();
            }
        }

        final DBCursor cursor = collection.find();

        try {
            final BasicDBList returns = new BasicDBList();
            DBObject doc;

            while (cursor.hasNext()) {
                doc = cursor.next();
                final String product = ((String) ((DBObject) doc.get("coordinates")).get("product"));
                if (userFavourites.containsField(product)) {
                    doc.put("favourite", userFavourites.get(product));
                } else {
                    doc.put("favourite", false);
                }
                returns.add(doc);
            }

            return returns;
        } finally {
            cursor.close();
        }
    }

    @GET
    @Produces("application/json")
    @Path("/featureIndex/{product}/{major}.{minor}.{servicePack}/{build}")
    public DBObject getFeatureIndexForReport(@BeanParam final Coordinates coordinates,
            @QueryParam("searchText") final String searchText, @QueryParam("viewPassed") final Integer viewPassed,
            @QueryParam("viewFailed") final Integer viewFailed,
            @QueryParam("viewUndefined") final Integer viewUndefined,
            @QueryParam("viewSkipped") final Integer viewSkipped, @QueryParam("start") final String start) {

        final BasicDBObject example = QueryBuilder.getInstance().buildFilterQuery(coordinates, searchText,
                viewPassed, viewFailed, viewUndefined, viewSkipped, start);
        final DB db = this.client.getDB("bdd");
        final DBCollection featuresCollection = db.getCollection("features");
        final DBCursor features = featuresCollection
                .find(example,
                        new BasicDBObject("id", 1).append("name", 1).append("calculatedStatus", 1)
                                .append("originalAutomatedStatus", 1).append("tags", 1).append("uri", 1))
                .sort(Coordinates.getFeatureSortingObject());
        final BasicDBList featureIndex = new BasicDBList();
        try {
            for (final Object o : features) {
                featureIndex.add(o);
            }
        } finally {
            features.close();
        }
        return featureIndex;
    }

    protected void embedTestingTips(final BasicDBList featureList, final Coordinates coordinates, final DB db) {
        for (final Object feature : featureList) {
            Feature.embedTestingTips((DBObject) feature, coordinates, db);
        }
    }

    protected String s4() {
        return Double.toHexString(Math.floor((1 + Math.random()) * 0x10000)).substring(1);
    }

    /**
     * Generates a GUID
     */
    protected String guid() {
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
    }

    /**
     * go through all the embedded content, store it to GridFS, replace the doc embeddings with a hyperlink to the saved content.
     */
    protected void embedSteps(final DBObject feature, final GridFS gridFS, final Coordinates coordinates) {
        final BasicDBList elements = (BasicDBList) feature.get("elements");
        final String featureId = (String) feature.get("_id");
        if (elements != null) {
            for (int j = 0; j < elements.size(); j++) {
                final DBObject scenario = (DBObject) elements.get(j);
                final String scenarioId = (String) scenario.get("_id");
                final BasicDBList steps = (BasicDBList) scenario.get("steps");
                if (steps != null) {
                    for (int k = 0; k < steps.size(); k++) {
                        final DBObject step = (DBObject) steps.get(k);
                        final BasicDBList embeddings = (BasicDBList) step.get("embeddings");
                        if (embeddings != null) {
                            for (int l = 0; l < embeddings.size(); l++) {
                                final DBObject embedding = (DBObject) embeddings.get(l);
                                final GridFSInputFile image = gridFS.createFile(
                                        Base64.decodeBase64(((String) embedding.get("data")).getBytes()));
                                image.setFilename(guid());
                                final BasicDBObject metadata = new BasicDBObject()
                                        .append("product", coordinates.getProduct())
                                        .append("major", coordinates.getMajor())
                                        .append("minor", coordinates.getMinor())
                                        .append("servicePack", coordinates.getServicePack())
                                        .append("build", coordinates.getBuild()).append("feature", featureId)
                                        .append("scenario", scenarioId);
                                image.setMetaData(metadata);
                                image.setContentType((String) embedding.get("mime_type"));
                                image.save();
                                embeddings.put(l, image.getFilename());
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * go through find all the backgrounds elements and nest them in their scenarios (simplifies application logic downstream)
     */
    protected void packBackgroundsInToScenarios(final DBObject feature) {
        final List<DBObject> packedScenarios = new ArrayList<DBObject>();
        // go through all the backgrounds /scenarios
        final BasicDBList elements = (BasicDBList) feature.get("elements");
        if (elements != null) {
            for (int i = 0; i < elements.size(); i++) {
                final DBObject element = (DBObject) elements.get(i);
                if (element.get("type").equals("background")) { // if its a background
                    ((DBObject) elements.get(i + 1)).put("background", element); // push it in to the next element.
                } else {
                    // assume this is a scenario/other top level element and push it to the packed array.
                    packedScenarios.add(element);
                }
            }
            elements.clear();
            elements.addAll(packedScenarios);
        }
    }

    protected void updateSummaryDocument(final DB bdd, final Coordinates coordinates) {
        // product and version are redundant for search, but ensure they're populated if the upsert results in an insert.
        final DBObject summaryQuery = new BasicDBObject("_id",
                coordinates.getProduct() + "/" + coordinates.getVersionString()).append("coordinates",
                        coordinates.getObject(Field.PRODUCT, Field.VERSION));
        final DBCollection summary = bdd.getCollection("summary");
        final DBObject summaryObject = summary.findOne(summaryQuery);
        if (summaryObject != null) { // lookup the summary document
            @SuppressWarnings("unchecked")
            final List<String> buildArray = (List<String>) summaryObject.get("builds");
            if (!buildArray.contains(coordinates.getBuild())) { // only update it if this build hasn't been added to it before.
                // Update index document version.
                summary.update(summaryQuery,
                        new BasicDBObject("$push", new BasicDBObject("builds", coordinates.getBuild())), true,
                        false);
            }
        } else {// if the report doesn't already exist... then add it.
            summary.update(summaryQuery,
                    new BasicDBObject("$push", new BasicDBObject("builds", coordinates.getBuild())), true, false);
        }
    }

    @GET
    @Produces("application/json")
    @Path("/tags/{product}/{major}.{minor}.{servicePack}/{build}")
    public DBObject getTagList(@BeanParam final Coordinates coordinates) {
        final DB bdd = this.client.getDB("bdd");
        final DBCollection features = bdd.getCollection("features");
        // Build objects for aggregation pipeline
        // id option: returns each tag with a list of associated feature ids
        final DBObject match = new BasicDBObject("$match", coordinates.getReportCoordinatesQueryObject());
        final DBObject fields = new BasicDBObject("tags.name", 1);
        fields.put("_id", 0); // comment out for id option
        final DBObject project = new BasicDBObject("$project", fields);
        final DBObject unwind = new BasicDBObject("$unwind", "$tags");
        final DBObject groupFields = new BasicDBObject("_id", "$tags.name");
        // groupFields.put("features", new BasicDBObject("$addToSet", "$_id")); //comment in for id option
        groupFields.put("amount", new BasicDBObject("$sum", 1));
        final DBObject group = new BasicDBObject("$group", groupFields);
        final DBObject sort = new BasicDBObject("$sort", new BasicDBObject("amount", -1));

        final AggregationOutput output = features.aggregate(match, project, unwind, group, sort);

        // get _ids from each entry of output.result
        final BasicDBList returns = new BasicDBList();
        for (final DBObject obj : output.results()) {
            final String id = obj.get("_id").toString();
            returns.add(id);
        }
        return returns;
    }

    protected void updateStatsDocument(final DB bdd, final Coordinates coordinates, final BasicDBList features) {
        // product and version are redundant for search, but ensure they're populated if the upsert results in an insert.
        final DBCollection statsCollection = bdd.getCollection("reportStats");
        final String id = coordinates.getProduct() + "/" + coordinates.getVersionString() + "/"
                + coordinates.getBuild();
        statsCollection.remove(new BasicDBObject("_id", id));
        final BasicDBObject stats = new BasicDBObject("coordinates", coordinates.getReportCoordinates());
        stats.put("_id", id);
        final BasicDBObject summary = new BasicDBObject();
        stats.put("summary", summary);
        final BasicDBObject feature = new BasicDBObject();
        stats.put("feature", feature);
        for (final Object ob : features) {
            final BasicDBList scenarios = (BasicDBList) ((DBObject) ob).get("elements");
            if (scenarios != null) {
                for (final Object o : scenarios) {
                    final String status = StatusHelper.getFinalScenarioStatus((DBObject) o, false).getTextName();
                    final Integer statusCounter = (Integer) summary.get(status);
                    if (statusCounter == null) {
                        summary.put(status, 1);
                    } else {
                        summary.put(status, statusCounter + 1);
                    }
                }
            }
        }
        statsCollection.save(stats);
    }

    @PUT
    @Path("/{product}/{major}.{minor}.{servicePack}/{build}")
    public DBObject putReport(@BeanParam final Coordinates coordinates, final DBObject root) throws IOException {
        final BasicDBList doc = (BasicDBList) root;
        final DB grid = this.client.getDB("grid");
        final GridFS gridFS = new GridFS(grid);
        final DB bdd = this.client.getDB("bdd");
        final DBCollection features = bdd.getCollection("features");
        updateSummaryDocument(bdd, coordinates);

        for (int i = 0; i < doc.size(); i++) {
            // take each feature and give it a unique id.
            final DBObject feature = (DBObject) doc.get(i);
            final String _id = coordinates.getFeature_Id((String) feature.get("id"));
            feature.put("_id", _id);
            embedSteps(feature, gridFS, coordinates); // extract embedded content and hyperlink to it.
            packBackgroundsInToScenarios(feature); // nest background elements within their scenarios
            final BasicDBObject featureCo = coordinates.getReportCoordinates();
            feature.put("coordinates", featureCo);

            final BasicDBList newElements = mergeExistingScenarios(features, feature, _id);
            feature.put("elements", newElements);

            final String originalStatus = StatusHelper.getFeatureStatus(feature);
            feature.put("calculatedStatus", originalStatus);
            feature.put("originalAutomatedStatus", originalStatus);
            this.log.info("Saving: " + feature.get("name") + " - " + feature.get("calculatedStatus"));
            this.log.trace("Adding feature:" + JSON.serialize(feature));
            features.save(feature);
        }
        final DBCursor cursor = features.find(coordinates.getReportCoordinatesQueryObject()); // get new co-ordinates to exclude the "version"
        // field
        final List<DBObject> returns = new ArrayList<DBObject>();
        try {
            while (cursor.hasNext()) {
                returns.add(cursor.next());
            }
        } finally {
            cursor.close();
        }
        final BasicDBList list = new BasicDBList();
        list.addAll(returns);
        updateStatsDocument(bdd, coordinates, list);
        return list;
    }

    private BasicDBList mergeExistingScenarios(final DBCollection features, final DBObject feature,
            final String _id) {
        BasicDBList newElements = (BasicDBList) feature.get("elements");
        if (newElements == null) {
            newElements = new BasicDBList();
        }
        final List<String> newElementIds = new ArrayList<String>();

        for (int k = 0; k < newElements.size(); k++) {
            final DBObject elem = (DBObject) newElements.get(k);
            final String elem_type = (String) elem.get("type");
            if (elem_type.equalsIgnoreCase("scenario")) {
                newElementIds.add((String) elem.get("id"));
            }
        }

        final DBObject existingFeature = features.findOne(_id);
        if (existingFeature != null) {
            final BasicDBList existingElements = (BasicDBList) existingFeature.get("elements");
            if (existingElements != null) {
                for (int j = 0; j < existingElements.size(); j++) {
                    final DBObject element = (DBObject) existingElements.get(j);
                    final String element_type = (String) element.get("type");
                    if (element_type.equalsIgnoreCase("scenario")) {
                        final String element_id = (String) element.get("id");
                        if (!newElementIds.contains(element_id)) {
                            newElements.add(element);
                        }
                    }
                }
            }

        }
        return newElements;
    }

    @POST
    @Path("/{product}/{major}.{minor}.{servicePack}/{build}")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response uploadFile(@BeanParam final Coordinates coord, @FormDataParam("file") final DBObject root,
            @FormDataParam("file") final FormDataContentDisposition fileDetail) throws IOException {
        putReport(coord, root);
        return Response.status(200).entity("success").build();

    }
}