de.iteratec.iteraplan.businesslogic.exchange.elasticmi.HbMappedClass.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.exchange.elasticmi.HbMappedClass.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.businesslogic.exchange.elasticmi;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.hibernate.MappingException;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Table;
import org.hibernate.metadata.ClassMetadata;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.error.IteraplanTechnicalException;
import de.iteratec.iteraplan.common.util.Preconditions;
import de.iteratec.iteraplan.model.AbstractHierarchicalEntity;
import de.iteratec.iteraplan.model.BuildingBlock;
import de.iteratec.iteraplan.model.attribute.AttributeType;

/**
 * This class provides all necessary data which is used for generating 
 * SubstantialTypeExpressions and RelationshipTypeExpressions;
 * There should be one HbMappedClass for each entity class / table of the iteraplan data model
 */
public class HbMappedClass {

    private static final Logger LOGGER = Logger.getIteraplanLogger(HbMappedClass.class);
    private static final String RELEASE = "Release";
    private static final String REV = "REV";
    public static final Map<String, String> OPPOSITE_NAMES = initOppositeNames();
    public static final Set<String> IGNORED_OPPOSITES = initIgnoreProperties();

    private ClassMetadata metaData;
    private Table table;
    private PersistentClass persistentClass;
    private Map<String, HbMappedProperty> properties;
    private Set<HbMappedClass> subClasses;

    private Map<String, HbMappedClass> allHbMappedClasses;

    /**
     * Use this constructor for abstract classes only!
     * (e.g. {@link AbstractHierarchicalEntity})
     * 
     * @param allHbMappedClasses holds the references to all other initialized {@link HbMappedClass}es
     * @param persistentClass the representing entity class
     */
    public HbMappedClass(Map<String, HbMappedClass> allHbMappedClasses, PersistentClass persistentClass) {
        this.allHbMappedClasses = Preconditions.checkNotNull(allHbMappedClasses);
        this.metaData = null;
        this.persistentClass = Preconditions.checkNotNull(persistentClass);
        this.table = persistentClass.getIdentityTable();
        initProperties();
    }

    /**
     * Standard Constructor
     * 
     * @param allHbMappedClasses holds the references to all other initialized HbMappedClasses
     * @param metaData contains all necessary hibernate mapping information
     * @param persistentClass the representing entity class ({@link PersistentClass})
     */
    public HbMappedClass(Map<String, HbMappedClass> allHbMappedClasses, ClassMetadata metaData,
            PersistentClass persistentClass) {

        this.allHbMappedClasses = Preconditions.checkNotNull(allHbMappedClasses);
        this.metaData = Preconditions.checkNotNull(metaData);
        this.persistentClass = Preconditions.checkNotNull(persistentClass);
        this.table = persistentClass.getIdentityTable();
        initProperties();
    }

    /**
     * Returns the represented class' name
     * @return
     *    the canonical name of the class if the instance is representing an automatically generated table (without a corresponding entity class, like HIST* tables)
     *    the simple name of the represented entity class
     */
    public String getClassName() {
        if (getMappedClass() == null) {
            return persistentClass.getEntityName();
        } else {
            return getMappedClass().getSimpleName();
        }
    }

    /**
     * Used from @see {@link HbMappedProperty}.{@link #getOpposite(HbMappedProperty)}
     */
    public Map<String, HbMappedClass> getAllHbMappedClasses() {
        return allHbMappedClasses;
    }

    /**
     * Searches for a corresponding back-ref
     * @see #OPPOSITE_NAMES
     * @see #IGNORED_OPPOSITES
     * 
     * @param prop the {@link HbMappedProperty}
     * @return the opposite (=back-ref) of the {@link HbMappedProperty}
     */
    HbMappedProperty getOpposite(HbMappedProperty prop) {
        if (!this.equals(prop.getContainingClass()) || !properties.containsKey(prop.getName())) {
            throw (new IteraplanTechnicalException());
        }
        String propName = prop.getName();
        if (REV.equals(propName) || IGNORED_OPPOSITES.contains(propName)) {
            // HistoryRevisionEntity does not contain a back-reference
            // some relationships don't have opposites (@see initIgnoreOpposites())
            return null;
        }
        Class<?> type = prop.getType();
        if (type.isEnum()) {
            LOGGER.warn("Opposite for enum requested!");
            return null;
        }
        HbMappedClass targetClass = prop.getHbType();
        if (targetClass == null) {
            LOGGER.warn("Could not find HbMappedClass for " + type.getCanonicalName());
            targetClass = this;
        }

        for (Entry<String, String> e : OPPOSITE_NAMES.entrySet()) {

            if (propName.equals(e.getKey())) {
                if (getProperty(e.getValue()) != null) {
                    targetClass = this;
                }
                return targetClass.getProperty(e.getValue());
            }
            if (propName.equals(e.getValue())) {
                if (getProperty(e.getKey()) != null) {
                    targetClass = this;
                }
                return targetClass.getProperty(e.getKey());
            }

        }

        ArrayList<HbMappedProperty> candidates = new ArrayList<HbMappedProperty>();
        for (HbMappedProperty hbProp : targetClass.getAllRelations()) {
            Class<?> propType = hbProp.getType();
            if (propType != null) {
                HbMappedClass propTypeHbClass = hbProp.getHbType();
                if (propTypeHbClass != null && propTypeHbClass.equals(this)) {
                    candidates.add(hbProp);
                }
            }
        }
        if (candidates.size() == 1) {
            return candidates.get(0);
        }
        LOGGER.debug("Could not determine opposite for HbMappedProperty " + getClassName() + "." + propName + ":");
        if (candidates.size() < 1) {
            LOGGER.debug("\tFound 0 candidates");
        } else {
            for (HbMappedProperty candidate : candidates) {
                LOGGER.debug("tFound " + candidate.getContainingClass().getClassName() + "." + candidate.getName());
            }
        }

        return null;
    }

    /**
     * Finds all {@link HbMappedClass}es representing subclasses of the 
     * class that is represented by this {@link HbMappedClass} instance
     * 
     * @return a {@link Set} of {@link HbMappedClass}es that are representing subclasses of this instance
     */
    public Set<HbMappedClass> getSubClasses() {
        if (!hasSubClasses()) {
            //just to make sure that subClasses has been initialized correctly
            return subClasses;
        }
        return subClasses;
    }

    /**
     * Decides whether the represented class is a "release"-class
     * (currently: InformationSystemRelease, TechnicalComponentRelease)
     * 
     * @return true, if {@link #getClassName()} contains "Release"
     */
    public boolean isReleaseClass() {
        return (getMappedClass() != null && getClassName().contains(RELEASE));
    }

    /**
     * Find the base class for "release"-classes
     * @see #isReleaseClass()
     * 
     * @return the base class for "release"-classes (e.g. InformationSystem for InformationSystemRelease, TechnicalComponent for TechnicalComponentRelease)
     */
    public HbMappedClass getReleaseBase() {
        if (!isReleaseClass() || getMappedClass() == null) {
            return null;
        }
        return allHbMappedClasses.get(getMappedClass().getCanonicalName().replace(RELEASE, ""));
    }

    public HbMappedProperty getReleaseBaseProperty() {
        if (!isReleaseClass()) {
            return null;
        }
        return properties.get(decapitalize(getReleaseBase().getClassName()));
    }

    /**
     * Necessary to ensure the correct merging of e.g. InformationSystemRelease + InformationSystem => InformationSystem+
     * 
     * @return true if there are {@link HbMappedClass}es 
     */
    public boolean hasReleaseClass() {
        return getReleaseClass() != null;
    }

    public HbMappedClass getReleaseClass() {
        if (getMappedClass() == null) {
            return null;
        }
        return allHbMappedClasses.get(getMappedClass().getCanonicalName() + RELEASE);
    }

    /**
     * Get the {@link HbMappedProperty} which is contained in the {@link HbMappedClass} 
     * 
     * @param propertyName the name of the property
     * 
     * @return the {@link HbMappedProperty} with the given propertyName; null, if no property with 
     * the propertyName exists 
     */
    public HbMappedProperty getProperty(String propertyName) {
        return properties.get(propertyName);
    }

    /**
     * 
     * @return true if the instance is representing an {@link Enum}
     */
    public boolean isEnum() {
        if (getMappedClass() != null && getMappedClass().isEnum()) {
            return true;
        }
        return false;
    }

    /**
     * 
     * @return the iteraplan entity class which is represented or null if the instance is representing an automatically generated table (like HIST*)
     */
    public final Class<?> getMappedClass() {
        return persistentClass.getMappedClass();
    }

    /**
     * Get all {@link HbMappedProperty}s of the {@link HbMappedClass}
     * 
     * @return all {@link HbMappedProperty}s; all properties which are defined in the entity class' 
     */
    public Collection<HbMappedProperty> getProperties() {
        return properties.values();
    }

    /**
     * Get the names of all properties which are defined in the represented entity class
     * 
     * @return a {@link List} of names; (Note: names of properties which are defined in super classes are being ignored)
     */
    public List<String> getPropertyNames() {
        if (metaData == null) {
            return Lists.newArrayList();
        }

        return Arrays.asList(metaData.getPropertyNames());
    }

    /**
     * @return true if the instance is representing a class that serves as a super class of another entity class (like {@link BuildingBlock}, {@link AttributeType}, ...)
     */
    public boolean hasSubClasses() {
        if (subClasses == null) {
            findSubclasses();
        }
        return !subClasses.isEmpty();
    }

    /**
     * Initialize {@link HbMappedProperty}s for all properties as 
     * defined by {@link ClassMetadata#getPropertyNames()}
     */
    @SuppressWarnings("unchecked")
    private final void initProperties() {
        properties = Maps.newHashMap();
        if (getMappedClass() != null && getMappedClass().getSuperclass().equals(Object.class) && metaData != null) {
            Property idProp = persistentClass.getIdentifierProperty();
            Column idCol = getColumn(idProp);
            HbMappedProperty idHbmProp = new HbMappedProperty(this, idProp, idCol);
            properties.put(metaData.getIdentifierPropertyName(), idHbmProp);
        }

        if (getMappedClass() == null) {
            //handle history revision entity
            Iterator<Column> colIterator = table.getColumnIterator();
            while (colIterator.hasNext()) {
                Column column = colIterator.next();
                createProperty(column);
            }
        } else if (metaData != null) {
            for (String propertyName : metaData.getPropertyNames()) {
                createProperty(propertyName);
            }
        }
    }

    /**
     * create a {@link HbMappedProperty} for tables that do not have a corresponding entity class
     * (e.g. HIST*)
     * Uses meta information of the {@link Column} instead.
     * 
     */
    private void createProperty(Column column) {
        HbMappedProperty newHbmProp = new HbMappedProperty(this, null, column);
        properties.put(column.getName(), newHbmProp);
    }

    /**
     * create a {@link HbMappedProperty} for a standard property 
     * 
     * @param propName the name of the property 
     */
    private void createProperty(String propName) {
        Property property = getPropertyFromPersistentClass(propName);
        if (property != null) {
            Column column = getColumn(property);
            HbMappedProperty newHbmProp = new HbMappedProperty(this, property, column);
            properties.put(propName, newHbmProp);
        }
    }

    /**
     * Get a {@link Property} for a propertyName
     * 
     * @param propertyName the property to search for
     * 
     * @return the {@link Property} with the given propertyName
     */
    private Property getPropertyFromPersistentClass(String propertyName) {
        try {
            return persistentClass.getProperty(propertyName);
        } catch (MappingException e) {
            return null;
        }

    }

    /**
     * Get a {@link Column} for a {@link Property}
     * 
     * @param property
     * @return the {@link Column};
     */
    private Column getColumn(Property property) {
        if (property == null || !property.getColumnIterator().hasNext()) {
            return null;
        }
        return (Column) property.getColumnIterator().next();
    }

    /**
     * 
     * @return the super class of the represented entity class (if it is different from {@link Object}.class)
     */
    private Class<?> getSuperClass(Class<?> clazz) {
        Class<?> c = clazz;
        if (c == null) {
            c = getMappedClass();
        }
        if (c == null || c.getSuperclass() == null || c.getSuperclass().equals(Object.class)) {
            return null;
        }
        return c.getSuperclass();
    }

    /**
     * 
     * @return the subset of {@link #getAllHbMappedProperties()} representing primitive properties (no relationships or enums)
     */
    public Collection<HbMappedProperty> getAllProperties() {
        Set<HbMappedProperty> hbProps = Sets.newHashSet();
        for (HbMappedProperty hbProp : getAllHbMappedProperties()) {
            if (hbProp.isPrimitive()) {
                hbProps.add(hbProp);
            }
        }
        return hbProps;
    }

    /**
     * 
     * @return the subset of {@link #getAllHbMappedProperties()} representing enumeration properties (no relationships or primitive properties)
     */
    public Collection<HbMappedProperty> getAllEnums() {
        Set<HbMappedProperty> hbProps = Sets.newHashSet();
        for (HbMappedProperty hbProp : getAllHbMappedProperties()) {
            if (hbProp.isEnum()) {
                hbProps.add(hbProp);
            }
        }
        return hbProps;
    }

    /**
     * 
     * @return the subset of {@link #getAllHbMappedProperties()} which represents relationships (no attributes or enums)
     */
    public Collection<HbMappedProperty> getAllRelations() {
        Set<HbMappedProperty> hbRels = Sets.newHashSet();
        for (HbMappedProperty hbRel : getAllHbMappedProperties()) {
            if (!hbRel.isPrimitive() && !hbRel.isEnum()) {
                hbRels.add(hbRel);
            }
        }
        return hbRels;
    }

    /**
     * Returns a {@link Collection} of all {@link HbMappedProperty}s that are defined for this class, the class' super class
     * (and properties that are defined in {@link #getReleaseBase()}) 
     */
    private Collection<HbMappedProperty> getAllHbMappedProperties() {
        Map<String, HbMappedProperty> allProps = Maps.newHashMap();
        HbMappedClass tmp = this;

        while (tmp != null) {
            for (HbMappedProperty hbProp : tmp.getProperties()) {
                if (!allProps.containsKey(hbProp.getName())) {
                    allProps.put(hbProp.getName(), hbProp);
                }
            }
            tmp = tmp.getHbSuperClass();
        }

        if (hasReleaseClass()) {
            String className = getMappedClass().getCanonicalName() + RELEASE;

            HbMappedClass releaseClass = getReleaseClass();
            if (releaseClass == null) {
                LOGGER.error("Could not find release class " + className);
            } else {
                for (HbMappedProperty hbProp : releaseClass.getProperties()) {
                    if (!allProps.containsKey(hbProp.getName())) {
                        allProps.put(hbProp.getName(), hbProp);
                    }
                }
            }
        }

        return allProps.values();
    }

    /**
     * 
     * @return the HbMappedClass that is representing the next available (i.e. not abstract) super class of the represented entity class
     */
    public HbMappedClass getHbSuperClass() {
        Class<?> superClass = getSuperClass(null);
        while (superClass != null && allHbMappedClasses.get(superClass.getName()) == null) {
            superClass = getSuperClass(superClass);
        }
        if (superClass == null || Object.class.equals(superClass)) {
            return null;
        } else {
            return allHbMappedClasses.get(superClass.getName());
        }
    }

    /**
     * @return the super class of the represented entity class
     */
    public Class<?> getSuperClass() {
        if (getMappedClass() == null) {
            return Object.class;
        } else {
            return getMappedClass().getSuperclass();
        }
    }

    /**
     * @return a nicer String representation than the default one
     */
    @Override
    public String toString() {
        String superName = "";
        if (getHbSuperClass() != null) {
            superName = " <- " + getHbSuperClass().getClassName();
        }
        if (subClasses == null) {
            findSubclasses();
        }

        return "Metadata for class " + getClassName() + superName + " (" + subClasses.size() + ")";
    }

    /**
     * Specifies special name pairs for Relationships among the iteraplan model. This is to make sure that RelationshipEndExpressions 
     * can be associated to the right RelationshipTypeExpressions.
     */
    private static ImmutableMap<String, String> initOppositeNames() {
        Map<String, String> map = Maps.newHashMap();
        map.put("parent", "children");
        map.put("predecessors", "successors");
        map.put("generalisation", "specialisations");
        map.put("informationSystemReleaseA", "interfacesReleaseA");
        map.put("informationSystemReleaseB", "interfacesReleaseB");
        map.put("elementOfRoles", "consistsOfRoles");
        map.put("baseComponents", "parentComponents");

        return ImmutableMap.copyOf(map);
    }

    /**
     * Some of the iteraplan references do not have a corresponding back-reference. The names of these relationships should be contained within this {@link ImmutableSet}.
     * By that, the method @see getOpposite() will return null before searching for a back-reference
     */
    private static ImmutableSet<String> initIgnoreProperties() {

        Set<String> set = Sets.newHashSet();
        set.add("buildingBlockType");
        set.add("runtimePeriod");
        set.add("resultBbType");

        return ImmutableSet.copyOf(set);
    }

    /**
     * Private helper method which initializes {@link #subClasses}, a {@link Set} of {@link HbMappedClass}es containing all instances representing subclasses of this instance
     */
    private void findSubclasses() {
        subClasses = Sets.newHashSet();
        if (getMappedClass() != null) {
            for (HbMappedClass candidate : allHbMappedClasses.values()) {
                if (candidate.getMappedClass() != null && this.equals(candidate.getHbSuperClass())) {
                    subClasses.add(candidate);
                }
            }
        }
    }

    private String decapitalize(String s) {
        return s.substring(0, 1).toLowerCase() + s.substring(1);
    }
}