io.onedecision.engine.decisions.web.DecisionDmnModelController.java Source code

Java tutorial

Introduction

Here is the source code for io.onedecision.engine.decisions.web.DecisionDmnModelController.java

Source

/*******************************************************************************
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package io.onedecision.engine.decisions.web;

import io.onedecision.engine.decisions.api.DecisionException;
import io.onedecision.engine.decisions.api.DecisionNotFoundException;
import io.onedecision.engine.decisions.api.NoDmnFileInUploadException;
import io.onedecision.engine.decisions.api.RepositoryService;
import io.onedecision.engine.decisions.impl.DecisionModelFactory;
import io.onedecision.engine.decisions.impl.IdHelper;
import io.onedecision.engine.decisions.impl.TransformUtil;
import io.onedecision.engine.decisions.model.dmn.Decision;
import io.onedecision.engine.decisions.model.dmn.DmnModel;
import io.onedecision.engine.decisions.repositories.DecisionDmnModelRepository;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.TransformerConfigurationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * Controller to manage native DMN decision definitions.
 * 
 * @author Tim Stephenson
 */
@Controller
@RequestMapping(value = "/{tenantId}/decision-models")
public class DecisionDmnModelController extends DecisionModelFactory implements RepositoryService {
    private static final Logger LOGGER = LoggerFactory.getLogger(DecisionDmnModelController.class);

    @Autowired
    private DecisionDmnModelRepository repo;

    protected TransformUtil transformUtil;

    protected TransformUtil getTransformUtil() {
        // if (transformUtil == null) {
        transformUtil = new TransformUtil();
        try {
            transformUtil.setXsltResources("/static/xslt/dmn2html.xslt");
        } catch (TransformerConfigurationException e) {
            LOGGER.error(e.getMessage(), e);
            throw new DecisionException("Unable to render decision model", e);
        }
        // }
        return transformUtil;
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#listForTenant(java.lang.String)
     */
    @Override
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody List<DmnModel> listForTenant(@PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("List decision models for tenant %1$s", tenantId));

        List<DmnModel> list = repo.findAllForTenant(tenantId);
        LOGGER.info(String.format("Found %1$s decision models", list.size()));

        return list;
    }

    @RequestMapping(value = "/{definitionOrInternalId}", method = RequestMethod.GET, produces = { "text/html" })
    public String getModelForTenantHtml(@PathVariable("definitionOrInternalId") String id,
            @PathVariable("tenantId") String tenantId, Model model) {
        LOGGER.info(String.format("Seeking decision model %1$s for tenant %2$s", id, tenantId));

        DmnModel dmnModel = null;
        try {
            dmnModel = getModelForTenant(Long.parseLong(id), tenantId);
        } catch (NumberFormatException e) {
            dmnModel = getModelForTenant(id, tenantId);
        } finally {
            model.addAttribute("dmnModel", dmnModel);
        }

        return "decisionModel";
    }

    @RequestMapping(value = "/{definitionId}/{drgElementId}", method = RequestMethod.GET, produces = {
            "text/html" })
    public String getDrgElementForTenantHtml(@PathVariable("definitionId") String definitionId,
            @PathVariable("drgElementId") String drgElementId, @PathVariable("tenantId") String tenantId,
            @RequestParam(required = false) String edit, Model model) {
        LOGGER.info(String.format("Seeking decision model %1$s.%2$s for tenant %3$s", definitionId, drgElementId,
                tenantId));

        DmnModel dmnModel = getModelForTenant(definitionId, tenantId);
        Decision decision = dmnModel.getDefinitions().getDecision(drgElementId);
        if (decision == null || decision.getDecisionTable() == null) {
            // these are currently uneditable, render via XSL
            model.addAttribute("dmnModel", dmnModel);
            model.addAttribute("decision", decision);
            Map<String, String> params = new HashMap<String, String>();
            params.put("drgElementId", drgElementId);
            params.put("edit", edit == null ? "false" : "true");
            model.addAttribute("decisionHtml", getTransformUtil().transform(dmnModel.getDefinitionXml(), params));
            model.addAttribute("edit", edit == null ? false : true);

            return "decision";
        } else {
            // these ARE editable
            return "/decisions-table.html";
        }
    }

    @RequestMapping(value = "/{definitionId}/{decisionId}", method = RequestMethod.GET, produces = {
            "application/json" })
    public @ResponseBody DmnModel getDecisionServiceForTenant(@PathVariable("definitionId") String definitionId,
            @PathVariable("decisionId") String decisionId, @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking decision model %1$s.%2$s for tenant %3$s", definitionId, decisionId,
                tenantId));

        DmnModel dmnModel = getModelForTenant(definitionId, tenantId);
        Decision decision = dmnModel.getDefinitions().getDecision(decisionId);
        if (decision == null) {
            throw new DecisionNotFoundException(String.format("Decision %1$s.%2$s not not exist for tenant %3$s",
                    definitionId, decisionId, tenantId));
        }
        // TODO should reduce this to only the requested or inferred decisions

        return dmnModel;
    }

    @RequestMapping(value = "/{definitionOrInternalId}", method = RequestMethod.GET, produces = {
            "application/json" })
    public @ResponseBody DmnModel getModelForTenantRestApi(@PathVariable("definitionOrInternalId") String id,
            @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking decision model %1$s for tenant %2$s", id, tenantId));

        try {
            return getModelForTenant(Long.parseLong(id), tenantId);
        } catch (NumberFormatException e) {
            return getModelForTenant(id, tenantId);
        }
    }

    @Override
    public @ResponseBody DmnModel getModelForTenant(@PathVariable("id") Long id,
            @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking decision model %1$s for tenant %2$s", id, tenantId));

        DmnModel model = repo.findOneForTenant(id, tenantId);
        if (model == null) {
            throw new DecisionNotFoundException(tenantId, id.toString());
        }
        // indexModel(model);
        LOGGER.debug(String.format("... result from db: %1$s", model));

        return model;
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#getModelForTenant(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public @ResponseBody DmnModel getModelForTenant(@PathVariable("definitionId") String definitionId,
            @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking decision model %1$s for tenant %2$s", definitionId, tenantId));

        DmnModel model = repo.findByDefinitionId(definitionId, tenantId);
        if (model == null) {
            throw new DecisionNotFoundException(
                    String.format("Decision model %1$s not not exist for tenant %2$s", definitionId, tenantId));
        }
        // indexModel(model);
        LOGGER.debug(String.format("... result from db: %1$s", model));

        return model;
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#getDmnForTenant(java.lang.String,
     *      java.lang.String)
     */
    @Override
    @RequestMapping(value = "/{id}.dmn", method = RequestMethod.GET, produces = { "application/xml" })
    public @ResponseBody String getDmnForTenant(@PathVariable("id") String id,
            @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking decision model (dmn) %1$s for tenant %2$s", id, tenantId));

        DmnModel model = repo.findByDefinitionId(id, tenantId);
        LOGGER.debug(String.format("... result from db: %1$s", model));

        return model.getDefinitionXml();
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#getImageForTenant(java.lang.String,
     *      java.lang.String)
     */
    @Override
    @RequestMapping(value = "/{id}.dmn", method = RequestMethod.GET, produces = { "image/png" })
    public @ResponseBody byte[] getImageForTenant(@PathVariable("id") String id,
            @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking decision model image %1$s for tenant %2$s", id, tenantId));

        DmnModel model = repo.findByDefinitionId(id, tenantId);
        LOGGER.debug(String.format("... result from db: %1$s", model));

        return model.getImage();
    }

    /**
     * Upload DMN representation of decision.
     * 
     * @param files
     *            DMN files posted in a multi-part request and optionally an
     *            image of it.
     * @return The model created in the repository.
     * @throws IOException
     *             If cannot parse the file.
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public @ResponseBody DmnModel handleFileUpload(@PathVariable("tenantId") String tenantId,
            @RequestParam(value = "deploymentMessage", required = false) String deploymentMessage,
            @RequestParam(value = "file", required = true) MultipartFile... files) throws IOException {
        LOGGER.info(String.format("Uploading dmn for: %1$s", tenantId));

        if (files.length > 2) {
            throw new IllegalArgumentException(String
                    .format("Expected one DMN file and optionally one image file but received %1$d", files.length));
        }

        String dmnContent = null;
        String dmnFileName = null;
        byte[] image = null;
        for (MultipartFile resource : files) {
            LOGGER.debug(String.format("Deploying file: %1$s", resource.getOriginalFilename()));
            if (resource.getOriginalFilename().toLowerCase().endsWith(".dmn")
                    || resource.getOriginalFilename().toLowerCase().endsWith(".dmn.xml")) {
                LOGGER.debug("... DMN resource");
                dmnContent = new String(resource.getBytes(), "UTF-8");
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("DMN: " + dmnContent);
                }
                dmnFileName = resource.getOriginalFilename();
            } else {
                LOGGER.debug("... non-DMN resource");
                image = resource.getBytes();
            }
        }

        if (dmnContent == null) {
            throw new NoDmnFileInUploadException();
        }
        if (deploymentMessage == null || deploymentMessage.length() == 0) {
            deploymentMessage = String.format("Deployed from file: %1$s", dmnFileName);
        }
        DmnModel dmnModel = new DmnModel(dmnContent, deploymentMessage, image, tenantId);
        dmnModel.setName(IdHelper.toName(dmnFileName));
        dmnModel.setDefinitionXml(dmnContent);
        return createModelForTenant(dmnModel);
    }

    /**
     * Experimental.
     * 
     * @param model
     */
    protected void indexModel(DmnModel model) {
        model.setName(model.getDefinitions().getName());
        model.setDescription(model.getDefinitions().getDescription());
        // for (Decision d : model.getDefinitions().getDecisions()) {
        // model.getDecisionIds().add(d.getId());
        // model.getDecisionNames().add(d.getName());
        // }
        // for (BusinessKnowledgeModel bkm : model.getDefinitions()
        // .getBusinessKnowledgeModels()) {
        // model.getBusinessKnowledgeModelIds().add(bkm.getId());
        // model.getBusinessKnowledgeModelNames().add(bkm.getName());
        // }
    }

    @RequestMapping(value = "/", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public @ResponseBody DmnModel createModelForTenant(@PathVariable("tenantId") String tenantId,
            @RequestBody(required = false) DmnModel dmnModel, HttpServletRequest request,
            HttpServletResponse response, UriComponentsBuilder uriBuilder) {
        if (dmnModel == null) {
            dmnModel = DmnModel.newModel();
        }

        // ensure no discrepancy between tenant in URL and in model
        dmnModel.setTenantId(tenantId);
        dmnModel = createModelForTenant(dmnModel);
        // uriBuilder.fromPath();
        String url = request.getRequestURL().append(dmnModel.getShortId()).toString();
        response.setHeader("Location", url);
        dmnModel.addLink(new Link(url));
        return dmnModel;
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#createModelForTenant(io.onedecision.engine.decisions.model.dmn.DmnModel)
     */
    @Override
    public DmnModel createModelForTenant(DmnModel model) {
        LOGGER.info(String.format("Creating decision model for tenant %1$s", model.getTenantId()));
        // indexModel(model);
        model.setDefinitionXml(model.serialize(model.getDefinitions()));
        return repo.save(model);
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#updateModelForTenant(java.lang.String,
     *      io.onedecision.engine.decisions.model.dmn.DmnModel,
     *      java.lang.String)
     */
    @Override
    @RequestMapping(value = "/{definitionId}", method = RequestMethod.PUT)
    public @ResponseBody void updateModelForTenant(@PathVariable("definitionId") String definitionId,
            @RequestBody DmnModel model, @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Updating decision model %2$s for tenant %1$s", tenantId, definitionId));

        if (!definitionId.equals(model.getDefinitionId())) {
            throw new IllegalStateException("Proposed model does not match the resource identifier");
        }
        // TODO perform checks that the model changes are not destructive.

        model.setDefinitionXml(model.serialize(model.getDefinitions()));
        repo.save(model);
    }

    @Override
    public void deleteModelForTenant(Long id, String tenantId) {
        DmnModel model = getModelForTenant(id, tenantId);
        if (model == null) {
            throw new DecisionNotFoundException(
                    String.format("Unable to find model for tenant %1$s with id %2$d", tenantId, id));
        }
        repo.delete(model);
    }

    /**
     * @see io.onedecision.engine.decisions.api.RepositoryService#deleteModelForTenant(java.lang.Long,
     *      java.lang.String)
     */
    @Override
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public @ResponseBody void deleteModelForTenant(@PathVariable("id") String id,
            @PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Deleting decision model %1$s for tenant %2$s", id, tenantId));

        DmnModel dmnModel = repo.findByDefinitionId(id, tenantId);
        if (dmnModel == null) {
            repo.delete(Long.valueOf(id));
        } else {
            repo.delete(dmnModel.getShortId());
        }
    }
}