net.triptech.metahive.model.Definition.java Source code

Java tutorial

Introduction

Here is the source code for net.triptech.metahive.model.Definition.java

Source

/*******************************************************************************
 * Copyright (c) 2012 David Harrison, Triptech Ltd.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 *
 * Contributors:
 *     David Harrison, Triptech Ltd - initial API and implementation
 ******************************************************************************/
package net.triptech.metahive.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
import javax.persistence.TypedQuery;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import net.triptech.metahive.CalculationParser;
import net.triptech.metahive.web.model.DefinitionFilter;

import org.apache.commons.lang.StringUtils;
import org.hibernate.annotations.Index;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.javabean.RooJavaBean;

/**
 * The Class Definition.
 */
@RooJavaBean
@RooJpaActiveRecord(finders = { "findDefinitionsByNameLike" })
public class Definition {

    /** The name. */
    @NotNull
    @Index(name = "definitionName")
    @Size(min = 1, max = 100)
    @Column(unique = true)
    private String name;

    /** The definition type. */
    @NotNull
    @Index(name = "definitionDefinitionType")
    @Enumerated(EnumType.STRING)
    private DefinitionType definitionType = DefinitionType.STANDARD;

    /** The data type. */
    @NotNull
    @Index(name = "definitionDataType")
    @Enumerated(EnumType.STRING)
    private DataType dataType;

    /** The calculation as an HTML string. e.g.
     * <span class="variable">D10</span> + <span class="variable">D11</span> */
    @Index(name = "definitionCalculation")
    @Lob
    private String calculation;

    /** The key value generator. */
    @NotNull
    @Enumerated(EnumType.STRING)
    private KeyValueGenerator keyValueGenerator;

    /** The key value access. */
    @NotNull
    @Enumerated(EnumType.STRING)
    private UserRole keyValueAccess;

    /** The definition descriptions. */
    @OrderBy("created DESC")
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "definition")
    private List<Description> descriptions = new ArrayList<Description>();

    /** The data sources. */
    @OrderBy("organisation ASC")
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "definition")
    private List<DataSource> dataSources = new ArrayList<DataSource>();

    /** The category. */
    @ManyToOne
    @Index(name = "definitionCategory")
    @NotNull
    private Category category;

    /** The applicability. */
    @NotNull
    @Enumerated(EnumType.STRING)
    private Applicability applicability;

    /** The summary definition. */
    @ManyToOne
    @Index(name = "definitionSummaryDefinition")
    private Definition summaryDefinition;

    /** The summarised definitions. */
    @OrderBy("name ASC")
    @OneToMany(mappedBy = "summaryDefinition")
    private List<Definition> summarisedDefinitions = new ArrayList<Definition>();

    /** The comments. */
    @OrderBy("created ASC")
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "definition")
    private List<Comment> comments = new ArrayList<Comment>();

    /** The show expanded flag. */
    @Transient
    boolean expanded = false;

    /**
     *The pre-update method.
     */
    @PreUpdate
    public final void preUpdate() {
        if (this.definitionType != DefinitionType.SUMMARY) {
            resetSummarisedDefinitions();
        }

        if (this.definitionType != DefinitionType.STANDARD) {
            if (this.dataSources != null) {
                for (DataSource ds : this.dataSources) {
                    ds.remove();
                }
                this.dataSources = null;
            }
        }
    }

    /**
     * Gets the calculation.
     *
     * @return the calculation
     */
    public final String getCalculation() {
        if (this.definitionType != DefinitionType.CALCULATED) {
            this.calculation = "";
        }
        return this.calculation;
    }

    /**
     * Gets the marked up calculation.
     *
     * @return the marked up calculation
     */
    public final String getPlainTextCalculation() {
        String plainText = StringUtils.replace(this.getCalculation(), "</span>", "");
        return StringUtils.replace(plainText, "<span class=\"variable\">", "");
    }

    /**
     * Test calculation.
     *
     * @return the string
     */
    public final String testCalculation() {

        StringBuilder result = new StringBuilder();

        String calc = this.getPlainTextCalculation();

        if (StringUtils.isNotBlank(this.getCalculation())) {
            Set<Long> variableIds = CalculationParser.parseVariableIds(calc);

            Map<Long, Double> values = new HashMap<Long, Double>();

            for (Long id : variableIds) {
                // Assume a value of 2 for all variable ids
                values.put(id, 2d);
            }

            result.append(CalculationParser.buildCalculation(calc, values));
            result.append(" = ");
            result.append(CalculationParser.performCalculation(calc, values));
        }
        return result.toString();
    }

    /**
     * Gets the description.
     *
     * @return the description
     */
    public final Description getDescription() {
        Description description = null;

        if (getDescriptions() != null && getDescriptions().size() > 0) {
            description = getDescriptions().get(0);
        }
        return description;
    }

    /**
     * Gets the calculated definitions.
     *
     * @return the calculated definitions
     */
    public final List<Definition> getCalculatedDefinitions() {

        List<Definition> calculatedDefinitions = new ArrayList<Definition>();

        if (StringUtils.isNotBlank(this.getCalculation())) {
            Set<Long> ids = CalculationParser.parseVariableIds(this.getPlainTextCalculation());
            for (Long id : ids) {
                Definition def = Definition.findDefinition(id);
                calculatedDefinitions.add(def);
            }
        }
        return calculatedDefinitions;
    }

    /**
     * Gets the summarised definitions.
     *
     * @return the summarised definitions
     */
    public final List<Definition> getSummarisedDefinitions() {
        if (this.summarisedDefinitions == null) {
            this.summarisedDefinitions = new ArrayList<Definition>();
        }
        if (this.definitionType != DefinitionType.SUMMARY && this.summarisedDefinitions.size() > 0) {
            this.summarisedDefinitions = new ArrayList<Definition>();
        }
        return this.summarisedDefinitions;
    }

    /**
     * Adds a summarised definition.
     *
     * @param definition the definition
     */
    public final void addSummarisedDefinition(Definition definition) {
        definition.setSummaryDefinition(this);
        getSummarisedDefinitions().add(definition);
    }

    /**
     * Reset the summarised definitions list.
     */
    public final void resetSummarisedDefinitions() {
        if (this.summarisedDefinitions != null) {
            for (Definition def : this.summarisedDefinitions) {
                def.setSummaryDefinition(null);
                def.persist();
            }
        }
    }

    /**
     * Adds a data source.
     *
     * @param dataSource the data source
     */
    public final void addDataSource(DataSource dataSource) {
        dataSource.setDefinition(this);
        getDataSources().add(dataSource);
    }

    /**
     * Adds a description.
     *
     * @param description the description
     */
    public final void addDescription(Description description) {
        description.setDefinition(this);
        getDescriptions().add(description);
    }

    /**
     * Adds a comment.
     *
     * @param comment the comment
     */
    public final void addComment(Comment comment) {
        comment.setDefinition(this);
        getComments().add(comment);
    }

    /**
     * The toString method.
     */
    public final String toString() {
        return getName();
    }

    /**
     * Find all of the definitions.
     *
     * @return an ordered list of definitions
     */
    public static List<Definition> findAllDefinitions() {
        return entityManager().createQuery("SELECT d FROM Definition d ORDER BY name ASC", Definition.class)
                .getResultList();
    }

    /**
     * A helper function to find summarised definitions
     * based on the supplied definition id.
     *
     * @param def the definition
     * @return the list
     */
    public static List<Definition> findSummarisedDefinitions(final Definition def) {

        TypedQuery<Definition> q = entityManager()
                .createQuery("SELECT d FROM Definition AS d WHERE d.summaryDefinition = :def", Definition.class);
        q.setParameter("def", def);

        return q.getResultList();
    }

    /**
     * Find the definition going by the supplied name.
     *
     * @param name
     *            the name
     * @return the definition
     */
    public static Definition findDefinitionByNameEquals(String name) {

        Definition definition = null;

        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("The name argument is required");
        }

        TypedQuery<Definition> q = entityManager()
                .createQuery("SELECT d FROM Definition AS d WHERE LOWER(d.name) = LOWER(:name)", Definition.class);
        q.setParameter("name", name);

        List<Definition> definitions = q.getResultList();

        if (definitions != null && definitions.size() > 0) {
            definition = definitions.get(0);
        }
        return definition;
    }

    /**
     * Find the definition which is the unique identifier.
     *
     * @param name
     *            the name
     * @return the definition
     */
    public static Definition findUniqueIdentifierDefinition() {

        Definition definition = null;

        TypedQuery<Definition> q = entityManager()
                .createQuery("SELECT d FROM Definition AS d WHERE d.dataType = :dataType", Definition.class);
        q.setParameter("dataType", DataType.TYPE_UNIQUEID);

        List<Definition> definitions = q.getResultList();

        if (definitions != null && definitions.size() > 0) {
            definition = definitions.get(0);
        }
        return definition;
    }

    /**
     * Find the top level definitions (i.e. those that are not summarised).
     *
     * @return the list of definitions
     */
    public static List<Definition> findTopLevelDefinitions() {
        return findTopLevelDefinitions(null);
    }

    /**
     * Find the top level definitions (i.e. those that are not summarised).
     *
     * @param applicability the applicability of the definition to filter by
     * @return the list of definitions
     */
    public static List<Definition> findTopLevelDefinitions(final Applicability applicability) {

        StringBuilder sql = new StringBuilder("SELECT d FROM Definition d");
        sql.append(" WHERE d.summaryDefinition IS NULL");

        if (applicability != null) {
            sql.append(" AND d.applicability = :applicability");
        }
        sql.append(" ORDER BY d.name ASC");

        TypedQuery<Definition> q = entityManager().createQuery(sql.toString(), Definition.class);
        if (applicability != null) {
            q.setParameter("applicability", applicability);
        }

        return q.getResultList();
    }

    /**
     * Find definitions that have data supplied by the supplied organisation.
     *
     * @param organisation the organisation to filter by
     * @return the list of definitions
     */
    public static List<Definition> findSubmissionDefinitions(final Organisation organisation) {

        List<Definition> definitions = new ArrayList<Definition>();

        if (organisation != null && organisation.getId() != null) {

            StringBuilder sql = new StringBuilder("SELECT d FROM Definition d");
            sql.append(" JOIN d.dataSources ds JOIN ds.organisation o");
            sql.append(" WHERE d.definitionType = :definitionType");
            sql.append(" AND o.id = :organisationId ORDER BY d.name ASC");

            TypedQuery<Definition> q = entityManager().createQuery(sql.toString(), Definition.class);
            q.setParameter("definitionType", DefinitionType.STANDARD);
            q.setParameter("organisationId", organisation.getId());

            definitions = q.getResultList();
        }
        return definitions;
    }

    /**
     * Find definitions for the supplied ids.
     *
     * @param definitionIds
     *            the definition ids
     * @return the list of definitions
     */
    public static List<Definition> findDefinitionEntries(final String[] definitionIds) {

        List<Definition> definitions = new ArrayList<Definition>();

        StringBuilder where = new StringBuilder();

        if (definitionIds != null) {
            for (String definitionId : definitionIds) {
                try {
                    Long id = new Long(definitionId);

                    if (id > 0) {
                        if (where.length() > 0) {
                            where.append(" OR ");
                        }
                        where.append("d.id = ");
                        where.append(id);
                    }
                } catch (Exception e) {
                    // Error casting to a Long - skip
                }
            }
        }

        if (where.length() > 0) {
            StringBuilder sql = new StringBuilder("SELECT d FROM Definition d WHERE ");
            sql.append(where.toString());
            sql.append(" ORDER BY d.name ASC");

            definitions = entityManager().createQuery(sql.toString(), Definition.class).getResultList();
        }
        return definitions;
    }

    /**
     * Find definition entries.
     *
     * @param filter the definition filter
     * @param firstResult the first result
     * @param maxResults the max results
     * @return the list
     */
    public static List<Definition> findDefinitionEntries(final DefinitionFilter filter, final int firstResult,
            final int maxResults) {

        StringBuilder sql = new StringBuilder("SELECT d FROM Definition d JOIN d.category c");
        sql.append(buildWhere(filter));
        sql.append(" ORDER BY d.name ASC");

        TypedQuery<Definition> q = entityManager().createQuery(sql.toString(), Definition.class)
                .setFirstResult(firstResult).setMaxResults(maxResults);

        HashMap<String, String> variables = buildVariables(filter);
        for (String variable : variables.keySet()) {
            q.setParameter(variable, variables.get(variable));
        }

        return q.getResultList();
    }

    /**
     * Count the definitions.
     *
     * @param filter the filter
     * @return the long
     */
    public static long countDefinitions(final DefinitionFilter filter) {

        StringBuilder sql = new StringBuilder("SELECT COUNT(d) FROM Definition d JOIN d.category c");
        sql.append(buildWhere(filter));

        TypedQuery<Long> q = entityManager().createQuery(sql.toString(), Long.class);

        HashMap<String, String> variables = buildVariables(filter);
        for (String variable : variables.keySet()) {
            q.setParameter(variable, variables.get(variable));
        }

        return q.getSingleResult();
    }

    /**
     * Find the definitions that could be potentially related to the supplied definition.
     *
     * @param definition the definition
     * @return the list
     */
    public static List<Definition> findPotentialRelatedDefinitions(final Definition definition) {

        List<Definition> relatedDefinitions = new ArrayList<Definition>();
        HashMap<String, Object> variables = new HashMap<String, Object>();

        StringBuilder sql = new StringBuilder();

        if (definition != null) {
            StringBuilder where = new StringBuilder();

            List<Definition> defs = new ArrayList<Definition>();

            if (definition.getDefinitionType() == DefinitionType.CALCULATED) {
                defs = definition.getCalculatedDefinitions();
            }
            if (definition.getDefinitionType() == DefinitionType.SUMMARY) {
                defs = definition.getSummarisedDefinitions();
            }

            for (Definition def : defs) {
                if (where.length() > 0) {
                    where.append(" AND ");
                }
                where.append("d.id != ");
                where.append(def.getId());
            }
            if (where.length() > 0) {
                where.insert(0, "(");
                where.append(")");
            }

            if (definition.getDefinitionType() == DefinitionType.CALCULATED) {
                if (where.length() > 0) {
                    where.append(" AND ");
                }
                where.append("(d.dataType = :dataType1 OR d.dataType = :dataType2");
                where.append(" OR d.dataType = :dataType3)");

                variables.put("dataType1", DataType.TYPE_CURRENCY);
                variables.put("dataType2", DataType.TYPE_NUMBER);
                variables.put("dataType3", DataType.TYPE_PERCENTAGE);
            }

            if (definition.getDefinitionType() == DefinitionType.SUMMARY) {
                if (where.length() > 0) {
                    where.append(" AND ");
                }
                where.append("d.definitionType != :definitionType");
                where.append(" AND d.summaryDefinition is null");

                variables.put("definitionType", DefinitionType.SUMMARY);
            }

            if (definition.getDefinitionType() == DefinitionType.CALCULATED
                    || definition.getDefinitionType() == DefinitionType.SUMMARY) {
                // Load all of the definitions, less the definitions already associated
                sql.append("SELECT d FROM Definition d");

                if (where.length() > 0) {
                    sql.append(" WHERE ");
                    sql.append(where.toString());
                }
                sql.append(" ORDER BY d.name ASC");
            }
        }

        if (sql.length() > 0) {
            TypedQuery<Definition> q = entityManager().createQuery(sql.toString(), Definition.class);
            for (String variable : variables.keySet()) {
                q.setParameter(variable, variables.get(variable));
            }
            relatedDefinitions = q.getResultList();
        }

        return relatedDefinitions;
    }

    /**
     * Find the calculated definitions that include the supplied
     * definition as a calculation variable.
     *
     * @param definition the definition
     * @return the list
     */
    public static List<Definition> findDefinitionsWithVariable(final Definition definition) {

        List<Definition> definitions = new ArrayList<Definition>();

        if (definition != null && definition.getId() != null) {

            StringBuilder calculation = new StringBuilder("%<span class=\"variable\">D");
            calculation.append(definition.getId());
            calculation.append("</span>%");

            StringBuilder sql = new StringBuilder("SELECT d FROM Definition d");
            sql.append(" WHERE upper(d.calculation) LIKE :calculation");
            sql.append(" AND d.definitionType = :definitionType");

            TypedQuery<Definition> q = entityManager().createQuery(sql.toString(), Definition.class);
            q.setParameter("calculation", calculation.toString().toUpperCase());
            q.setParameter("definitionType", DefinitionType.CALCULATED);

            definitions = q.getResultList();
        }
        return definitions;
    }

    /**
     * Group the supplied definitions - does not include any unique id data types.
     *
     * @param definitions the definitions
     * @return the map
     */
    public static Map<String, List<Definition>> groupDefinitions(final List<Definition> definitions) {

        Map<String, List<Definition>> groupedDefinitions = new TreeMap<String, List<Definition>>();

        for (Definition definition : definitions) {
            if (definition.getDataType() != DataType.TYPE_UNIQUEID) {
                String name = definition.getCategory().getName();

                List<Definition> subGroup = new ArrayList<Definition>();

                if (groupedDefinitions.containsKey(name)) {
                    subGroup = groupedDefinitions.get(name);
                }
                subGroup.add(definition);

                groupedDefinitions.put(name, subGroup);
            }
        }
        return groupedDefinitions;
    }

    /**
     * Builds the where statement.
     *
     * @param filter the filter
     * @return the string
     */
    private static String buildWhere(final DefinitionFilter filter) {
        StringBuilder where = new StringBuilder();

        if (StringUtils.isNotBlank(filter.getName())) {
            where.append("LOWER(d.name) LIKE LOWER(:name)");
        }

        if (StringUtils.isNotBlank(filter.getCategory())) {
            if (where.length() > 0) {
                where.append(" AND ");
            }
            where.append("LOWER(c.name) LIKE LOWER(:category)");
        }

        if (where.length() > 0) {
            where.insert(0, " WHERE ");
        }
        return where.toString();
    }

    /**
     * Builds the variables for the where statement.
     *
     * @param filter the filter
     * @return the hash map
     */
    private static HashMap<String, String> buildVariables(final DefinitionFilter filter) {

        HashMap<String, String> variables = new HashMap<String, String>();

        if (StringUtils.isNotBlank(filter.getName())) {
            variables.put("name", "%" + filter.getName() + "%");
        }

        if (StringUtils.isNotBlank(filter.getCategory())) {
            variables.put("category", filter.getCategory());
        }
        return variables;
    }

}