io.onedecision.engine.domain.web.DomainController.java Source code

Java tutorial

Introduction

Here is the source code for io.onedecision.engine.domain.web.DomainController.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.domain.web;

import io.onedecision.engine.domain.model.DomainEntity;
import io.onedecision.engine.domain.model.DomainModel;
import io.onedecision.engine.domain.model.EntityField;
import io.onedecision.engine.domain.repositories.DomainModelRepository;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.multipart.MultipartFile;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Controller to access a tenant's domain model.
 * 
 * <p>
 * The domain model, sometimes called a data dictionary, for a tenant is the
 * common language and terminology for the application and specifically for the
 * decisions embedded.
 * 
 * @author Tim Stephenson
 *
 */
@Controller
@RequestMapping(value = "/{tenantId}/domain-model")
public class DomainController {
    private static final Logger LOGGER = LoggerFactory.getLogger(DomainController.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private DomainModelRepository repo;

    /**
     * @param tenantId
     *            The tenant id.
     * @return The domain model for the specified tenant or a default customer
     *         management example if the tenant is not known.
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody DomainModel getModelForTenant(@PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Seeking domain model for tenant %1$s", tenantId));

        DomainModel model = repo.findByName(tenantId);
        LOGGER.debug(String.format("... result: %1$s", model));

        if (model != null) {
            LOGGER.info("... found in repository");
            return model;
        } else {
            LOGGER.info("... not found, reliant on defaults");
            return getDefaultDomain();
        }
    }

    protected DomainModel getDefaultDomain() {
        DomainModel model = new DomainModel();
        model.setName("Example customer model");
        model.setDescription("A general purpose and extensible customer model for the web");

        List<DomainEntity> entities = new ArrayList<DomainEntity>();

        // Contact
        DomainEntity entity = new DomainEntity();
        entity.setName("Contact");
        entity.setDescription(
                "A Contact is associated with up to one Account, zero to many Notes and zero to many Documents. An Account typically has one contact though may have more.");
        entity.setImageUrl("images/domain/contact-context.png");
        List<EntityField> fields = new ArrayList<EntityField>();
        fields.add(new EntityField("firstName", "First Name", "Your first or given name", true, "text"));
        fields.add(new EntityField("lastName", "Last Name", "Your last or family name", true, "text"));
        fields.add(new EntityField("title", "Title", "Your title / salutation", false, "text"));
        fields.add(new EntityField("email", "Email Address", "Your business email address", true, "text"));
        fields.add(new EntityField("phone1", "Preferred Phone Number", "Your preferred telephone number", false,
                "tel", "\\+?[0-9, ]{0,13}"));
        fields.add(new EntityField("phone2", "Other Phone Number", "A backup telephone number", false, "tel",
                "\\+?[0-9, ]{0,13}"));
        fields.add(new EntityField("address1", "Address", "House or apartment name or number", false, "text"));
        fields.add(new EntityField("address2", "", "Stree", false, "text"));
        fields.add(new EntityField("countyOrCity", "City or County", "", false, "text"));
        fields.add(new EntityField("postCode", "Post Code", "Postal code, for example in London N1 9DH", false,
                "text", ""));
        fields.add(new EntityField("stage", "Stage", "The point in the sales funnel of this lead", false, "text"));
        fields.add(new EntityField("enquiryType", "Enquiry Type",
                "The nature of the enquiry, typically specific to the tenant's business", false, "text"));
        fields.add(new EntityField("accountType", "Account Type", "Customer, Partner etc.", false, "text"));
        fields.add(new EntityField("owner", "Owner", "The sales person for this account", false, "text"));
        fields.add(new EntityField("doNotCall", "Do Not Call", "Is it ok to call this lead?", false, "boolean"));
        fields.add(new EntityField("doNotEmail", "Do Not Email", "Is it ok to email this lead?", false, "boolean"));
        fields.add(new EntityField("timeSinceEmail", "Time since email",
                "Time since our last email to contact (milliseconds)", false, "number"));
        fields.add(new EntityField("timeSinceLogin", "Time since login", "Time since last login (milliseconds)",
                false, "number"));
        fields.add(new EntityField("timeSinceRegistered", "Time since registered",
                "Time since last registered (milliseconds)", false, "number"));
        fields.add(new EntityField("tenantId", "Tenant", "Name of the tenant's account", true, "text"));
        fields.add(new EntityField("firstContact", "First Contact", "Date of first contact with this business",
                true, "date"));
        fields.add(new EntityField("lastUpdated", "Last Updated", "Date of last update to this account", true,
                "date"));
        entity.setFields(fields);
        entities.add(entity);

        // Account
        entity = new DomainEntity();
        entity.setName("Account");
        entity.setDescription(
                "An account is associated with zero to many Notes and zero to many Documents. It typically has one Contact though may have more.");
        entity.setImageUrl("images/domain/account-context.png");
        fields = new ArrayList<EntityField>();
        fields.add(new EntityField("name", "Name", "Name of the company or organisation", true, "text"));
        fields.add(new EntityField("companyNumber", "Company Number",
                "The number for this company issued by the registrar of companies in your country.", false,
                "number"));
        fields.add(new EntityField("businessWebsite", "Business Website", "The primary website for the business",
                false, "url"));
        fields.add(new EntityField("description", "Description", "A fuller description", false, "text"));
        fields.add(new EntityField("tenantId", "Tenant", "Name of the tenant's account", true, "text"));
        fields.add(new EntityField("firstContact", "First Contact", "Date of first contact with this business",
                true, "date"));
        fields.add(new EntityField("lastUpdated", "Last Updated", "Date of last update to this account", true,
                "date"));
        entity.setFields(fields);
        entities.add(entity);

        // Email actions
        entity = new DomainEntity();
        entity.setName("Email");
        entity.setDescription("An email is an action (conclusion) available to the decision table authors.");
        entity.setImageUrl("images/domain/email-context.png");
        fields = new ArrayList<EntityField>();
        fields.add(new EntityField("subjectLine", "Subject Line", "Subject line for the email", true, "text"));
        fields.add(new EntityField("templateName", "Template Name", "Name of email template to use", true, "text"));
        entity.setFields(fields);
        entities.add(entity);

        model.setEntities(entities);
        return model;
    }

    /**
     * Imports JSON representation of contacts.
     * 
     * <p>
     * This is a handy link: http://shancarter.github.io/mr-data-converter/
     * 
     * @param file
     *            A file posted in a multi-part request
     * @return The meta data of the added model
     * @throws IOException
     *             If cannot parse the JSON.
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public @ResponseBody DomainModel handleFileUpload(@PathVariable("tenantId") String tenantId,
            @RequestParam(value = "file", required = true) MultipartFile file) throws IOException {
        LOGGER.info(String.format("Uploading domain model for: %1$s", tenantId));
        String content = new String(file.getBytes());

        DomainModel model = objectMapper.readValue(content, new TypeReference<DomainModel>() {
        });
        model.setTenantId(tenantId);

        LOGGER.info(String.format("  found model with %1$d entities", model.getEntities().size()));
        updateModelForTenant(tenantId, model);

        return model;
    }

    /**
     * Model updates are typically additive but for the time being at least this
     * is not enforced.
     * 
     * @param tenantId
     *            The tenant whose model is to be updated.
     * @param model
     *            The new domain model.
     */
    @RequestMapping(value = "/", method = RequestMethod.PUT)
    public @ResponseBody DomainModel updateModelForTenant(@PathVariable("tenantId") String tenantId,
            @RequestBody DomainModel model) {
        LOGGER.info(String.format("Updating domain model for tenant %1$s", tenantId));
        model.setTenantId(tenantId);
        for (DomainEntity entity : model.getEntities()) {
            entity.setTenantId(tenantId);
        }

        DomainModel existingModel = repo.findByName(tenantId);
        if (existingModel == null) {
            model.setRevision(1l);
        } else {
            // TODO perform checks that the model changes are not destructive.
            model.setRevision(existingModel.getRevision() + 1);
        }
        return repo.save(model);
    }

    /**
     * Update a single entity within the tenant's model.
     * 
     * @param tenantId
     *            The tenant whose model is to be updated.
     * @param entityName
     *            Name of the entity being updated.
     */
    @RequestMapping(value = "/{entityName}", method = RequestMethod.PUT)
    public @ResponseBody void updateEntityForTenant(@PathVariable("tenantId") String tenantId,
            @PathVariable("entityName") String entityName, @RequestBody DomainEntity entity) {
        LOGGER.info(String.format("Updating domain model for tenant %1$s with entity %2$s", tenantId, entityName));
        entity.setTenantId(tenantId);
        DomainModel model = getModelForTenant(tenantId);
        int idx = -1;
        for (int i = 0; i < model.getEntities().size(); i++) {
            if (entityName.equals(model.getEntities().get(i).getName())) {
                idx = i;
            }
        }
        if (idx != -1) {
            model.getEntities().remove(idx);
        }
        model.getEntities().add(entity);

        updateModelForTenant(tenantId, model);
    }

    /**
     * Deleting a model does not remove any application data, just the
     * meta-data. This can be useful to reset a clean environment after testing
     * for example.
     * 
     * @param tenantId
     *            The tenant whose model is to be removed.
     */
    @RequestMapping(value = "/", method = RequestMethod.DELETE)
    public @ResponseBody void deleteModelForTenant(@PathVariable("tenantId") String tenantId) {
        LOGGER.info(String.format("Deleting domain model for tenant %1$s", tenantId));

        repo.delete(repo.findByName(tenantId));
    }
}