de.iteratec.iteraplan.businesslogic.exchange.elasticExcel.excelimport.EntityDataImporter.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.exchange.elasticExcel.excelimport.EntityDataImporter.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.elasticExcel.excelimport;

import static de.iteratec.iteraplan.businesslogic.exchange.elasticExcel.export.template.AbstractSheetGenerator.FIRST_DATA_ROW_NO;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;

import de.iteratec.iteraplan.businesslogic.exchange.elasticExcel.util.ExcelUtils;
import de.iteratec.iteraplan.businesslogic.exchange.elasticExcel.workbookdata.ColumnContext;
import de.iteratec.iteraplan.businesslogic.exchange.elasticExcel.workbookdata.SheetContext;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.UserContext;
import de.iteratec.iteraplan.elasticeam.metamodel.EnumerationExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.EnumerationLiteralExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.EnumerationPropertyExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.FeatureExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.Metamodel;
import de.iteratec.iteraplan.elasticeam.metamodel.PropertyExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.RelationshipEndExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.RelationshipTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.SubstantialTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.UniversalTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.builtin.BuiltinPrimitiveType;
import de.iteratec.iteraplan.elasticeam.model.InstanceExpression;
import de.iteratec.iteraplan.elasticeam.model.Model;
import de.iteratec.iteraplan.elasticeam.model.UniversalModelExpression;
import de.iteratec.iteraplan.model.RuntimePeriod;

/**
 * Import FeatureExpressions and add them to the <b>model</b>.
 * 
 * <p>
 * Do so in two passes: first create the instances and import the primitives,
 * and second set the relations given on the STE sheets.
 * (Relation sheets will be handled separately in {@link RelationDataImporter}.)
 * 
 * <p>
 * Use the {@link Pass} value to distinguish between these steps.
 */
public class EntityDataImporter extends AbstractDataImporter {

    /** Logger. */
    private static final Logger LOGGER = Logger.getIteraplanLogger(EntityDataImporter.class);

    /** distinguish between third and fourth import pass. */
    public enum Pass {
        PRIMITIVES, RELATIONS
    }

    private Metamodel metamodel;
    private Model model;

    private Pass pass;

    /** map from special key (composed of type and Excel row) to instances. */
    private Map<String, UniversalModelExpression> instanceMap = new HashMap<String, UniversalModelExpression>();

    /**
     * Constructor.
     * 
     * @param metamodel
     * @param model
     */
    public EntityDataImporter(Metamodel metamodel, Model model) {
        this.model = model;
        this.metamodel = metamodel;
    }

    /**
     * do the import of the given sheet.
     * 
     * @param sheetContext
     * @param importPass
     *     PRIMITIVES for third pass, i.e. create instances and import primitives;
     *     RELATIONS for fourth pass, i.e. create relations.
     */
    public void importEntityData(SheetContext sheetContext, Pass importPass) {
        this.pass = importPass;
        LOGGER.info("    == Pass {0}: Sheet {1}",
                (pass == Pass.PRIMITIVES ? Integer.valueOf(3) : Integer.valueOf(4)), sheetContext.getSheetName());

        if (sheetContext.getExpression() instanceof SubstantialTypeExpression
                || sheetContext.getExpression() instanceof RelationshipTypeExpression) {
            importEntities(sheetContext);
        } else if (sheetContext.getExpression() instanceof EnumerationExpression) {
            return;
        } else {
            logError("Sheet {0}: Unexpected sheet type expression: {1}", sheetContext.getSheetName(),
                    sheetContext.getExpression().getClass().getName());
        }
    }

    /**
     * @param sheetContext
     */
    private void importEntities(SheetContext sheetContext) {
        boolean hasMore = true;
        for (int rowNum = FIRST_DATA_ROW_NO; hasMore; rowNum++) {
            Row row = sheetContext.getSheet().getRow(rowNum);
            hasMore = importEntity(sheetContext, row);
        }
    }

    private boolean importEntity(SheetContext sheetContext, Row row) {

        if (!containsData(sheetContext, row)) {
            return false;
        }

        UniversalTypeExpression entityType = (UniversalTypeExpression) sheetContext.getExpression();
        UniversalModelExpression instance = getInstance(entityType, row.getRowNum() - FIRST_DATA_ROW_NO);

        if (instance == null) {
            return false;
        }

        for (int col = 0; col < sheetContext.getColumnCount(); col++) {
            ColumnContext column = sheetContext.getColumns().get(col);
            FeatureExpression<?> featureExpression = column.getFeatureExpression();

            Cell cell = row.getCell(col);
            if (BuiltinPrimitiveType.DURATION.equals(featureExpression.getType())) {
                if (pass == Pass.PRIMITIVES) {
                    Cell endCell = row.getCell(col + 1);
                    importRuntimePeriodExpression(instance, featureExpression, cell, endCell);
                }
                col++;
            } else if (cell != null) { // cell may be null if the value is not set and rows are not created by the template generator.
                importFeatureExpression(instance, featureExpression, cell);
            }
        }

        return true;
    }

    /**
     * @param sheetContext
     * @param row
     * @return true if row contains any data (i.e. at least on cell has a value), and false if it is empty.
     */
    private boolean containsData(SheetContext sheetContext, Row row) {
        if (row != null) {
            for (int i = 0; i < sheetContext.getColumnCount(); i++) {
                if (!ExcelUtils.isEmptyCell(row.getCell(i))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * RuntimePeriod get special handling, because they need two columns for one primitive type.
     * 
     * @param instance
     * @param featureExpression
     * @param startCell
     * @param endCell
     */
    private void importRuntimePeriodExpression(UniversalModelExpression instance,
            FeatureExpression<?> featureExpression, Cell startCell, Cell endCell) {

        try {
            Date startDate = (startCell != null) ? startCell.getDateCellValue() : null;
            Date endDate = (endCell != null) ? endCell.getDateCellValue() : null;

            if (startDate == null && endDate == null) {
                return;
            }

            if (startDate != null && endDate != null && startDate.getTime() > endDate.getTime()) {
                DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, UserContext.getCurrentLocale());
                logError("Near cell {0}: RuntimePeriod: from {1} until {2} in instance {3}",
                        ExcelUtils.getFullCellName(startCell), df.format(startDate), df.format(endDate), instance);
            } else {
                RuntimePeriod rt = new RuntimePeriod(startDate, endDate);
                setValue(instance, (PropertyExpression<?>) featureExpression, rt, startCell);
            }
        } catch (Exception e) {
            logError("Near cell {0}: Error importing runtime period in {1}: {2}",
                    ExcelUtils.getFullCellName(startCell), instance, e.getMessage());
        }
    }

    private UniversalModelExpression getInstance(UniversalTypeExpression entityType, int index) {
        String key = entityType.getPersistentName() + "-" + index;
        UniversalModelExpression result = instanceMap.get(key);

        if (result == null) {
            if (entityType instanceof SubstantialTypeExpression) {
                result = model.create((SubstantialTypeExpression) entityType);
            } else if (entityType instanceof RelationshipTypeExpression) {
                result = model.create((RelationshipTypeExpression) entityType);
            } else {
                logError("findOrCreateInstance: can not create instance of type {0}",
                        entityType.getPersistentName());
            }
            instanceMap.put(key, result);
        }

        return result;
    }

    /**
     * import PropertyExpression and RelationshipEndExpression in pass 3 and 4, respectively.
     * Ignores all other feature expressions.
     */
    private void importFeatureExpression(UniversalModelExpression instance, FeatureExpression<?> featureExpression,
            Cell cell) {
        if (featureExpression instanceof PropertyExpression && pass == Pass.PRIMITIVES) {
            importPropertyExpression(instance, (PropertyExpression<?>) featureExpression, cell);
        } else if (featureExpression instanceof RelationshipEndExpression && pass == Pass.RELATIONS) {
            importRelationshipEndExpression(instance, (RelationshipEndExpression) featureExpression, cell);
        }
    }

    /**
     * do the import of a Property Expression: read the value from the Excel cell, convert the value,
     * and set it in the model.
     * 
     * @param instance
     * @param propertyExpression
     * @param cell
     */
    private void importPropertyExpression(UniversalModelExpression instance,
            PropertyExpression<?> propertyExpression, Cell cell) {
        Object value = readCellValue(cell, propertyExpression);
        setValue(instance, propertyExpression, value, cell);
    }

    /**
     * @param instance
     * @param relEndExpression
     * @param cell
     */
    private void importRelationshipEndExpression(UniversalModelExpression instance,
            RelationshipEndExpression relEndExpression, Cell cell) {
        String value = ExcelUtils.getStringCellValue(cell);
        if (value == null || "".equals(value)) {
            return;
        }

        SubstantialTypeExpression targetType = (SubstantialTypeExpression) relEndExpression.getType();
        InstanceExpression target = model.findByName(targetType, value);

        if (target == null) {
            logError("Cell {0}: NOT FOUND: relation {1} from {2}[{3}] to {4}[{5}]",
                    ExcelUtils.getFullCellName(cell), //
                    relEndExpression.getPersistentName(), instance.getClass().getSimpleName(), instance.toString(),
                    targetType.getPersistentName(), value);
        } else {
            instance.connect(relEndExpression, target);
        }
    }

    /**
     * use the type information from parameter <code>property</code> to convert the value.
     * 
     * @param cellValue
     * @param property
     * @return the converted object. Null if input was null.
     */
    private Object readCellValue(Cell cell, PropertyExpression<?> property) {
        if (isReleaseName(property.getHolder(), property)) {
            return getNormalizedReleaseName(cell);
        } else if (ExcelUtils.isEmptyCell(cell)) {
            return null;
        } else {
            return readPropertyValue(cell, property);
        }

    }

    private Object readPropertyValue(Cell nonEmptyCell, PropertyExpression<?> nonReleasenameProperty) {
        if (nonReleasenameProperty instanceof EnumerationPropertyExpression) {
            return getEnumPropertyValue(nonEmptyCell, nonReleasenameProperty);
        } else if (nonReleasenameProperty.getType().equals(BuiltinPrimitiveType.INTEGER)) {
            Double doubleValue = getCellValueOfType(nonEmptyCell, Double.class);
            return doubleValue == null ? null : BigInteger.valueOf(doubleValue.longValue());
        } else if (nonReleasenameProperty.getType().equals(BuiltinPrimitiveType.DECIMAL)) {
            Double doubleValue = getCellValueOfType(nonEmptyCell, Double.class);
            return doubleValue == null ? null : BigDecimal.valueOf(doubleValue.doubleValue());
        } else if (nonReleasenameProperty.getType().equals(BuiltinPrimitiveType.DATE)) {
            return getCellValueOfType(nonEmptyCell, Date.class);
        } else if (nonReleasenameProperty.getType().equals(BuiltinPrimitiveType.BOOLEAN)) {
            String stringValue = getCellValueOfType(nonEmptyCell, String.class);
            return Boolean.valueOf(Boolean.parseBoolean(stringValue));
        } else if (nonReleasenameProperty.getType().equals(BuiltinPrimitiveType.STRING)) {
            return getStringPropertyValue(nonEmptyCell, nonReleasenameProperty);
        } else {
            LOGGER.info("Property type for Java enum: {0}", nonReleasenameProperty.getClass().getName());
            return resolveJavaEnum(nonReleasenameProperty.getType().getName(),
                    ExcelUtils.getCellValue(nonEmptyCell, false));
        }
    }

    private Object getEnumPropertyValue(Cell cell, PropertyExpression<?> property) {
        String stringCellValue = ExcelUtils.getStringCellValue(cell);
        if (StringUtils.isEmpty(stringCellValue)) {
            return null;
        } else if (property.getUpperBound() > 1) {
            // multi-value attributes may be in one cell, separated by ';'
            List<EnumerationLiteralExpression> list = new ArrayList<EnumerationLiteralExpression>();
            for (String literalName : stringCellValue.split(";")) {
                EnumerationLiteralExpression literal = findEnumLiteral(literalName, property);
                list.add(literal);
            }
            return list;
        } else {
            return findEnumLiteral(stringCellValue, property);
        }
    }

    private Object getStringPropertyValue(Cell cell, PropertyExpression<?> property) {
        String stringValue = ExcelUtils.getStringCellValue(cell);
        if (property.getUpperBound() > 1) {
            // multi-value attributes may be in one cell, separated by ';'
            if (stringValue == null || "".equals(stringValue)) {
                return new ArrayList<String>();
            } else {
                return Arrays.asList(stringValue.split(";"));
            }
        } else {
            return stringValue;
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T getCellValueOfType(Cell cell, Class<T> typeClass) {
        boolean convertNumberToDate = typeClass.equals(Date.class);
        Object cellValue = ExcelUtils.getCellValue(cell, convertNumberToDate);

        if (cellValue == null) {
            return null;
        }

        if (typeClass.isAssignableFrom(cellValue.getClass())) {
            return (T) cellValue;
        } else if (String.class.equals(typeClass)) {
            // In some cases, Excel stores text-formatted cells as numeric, if it contains only digits.
            // Number types can't be casted to String, but converted.
            // To avoid errors, handle non-String typed (Excel-/POI-) cells with Metamodel type "String" like this:
            return (T) ExcelUtils.getStringCellValue(cell);
        } else {
            logError("Cell {0}: Expected Cell value of type \"{1}\" but got \"{2}\"",
                    ExcelUtils.getFullCellName(cell), typeClass.getSimpleName(),
                    cellValue.getClass().getSimpleName());
            return null;
        }
    }

    /**
     * find the enum literal in the metamodel.
     * 
     * @param cellValue
     * @param property
     * @return the enum literal for the given cell.
     */
    private EnumerationLiteralExpression findEnumLiteral(Object cellValue, PropertyExpression<?> property) {
        EnumerationLiteralExpression result = null;
        String persistentName = property.getType().getPersistentName();
        String literalName = cellValue.toString();

        EnumerationExpression enumExpr = (EnumerationExpression) metamodel.findTypeByPersistentName(persistentName);
        if (enumExpr == null) {
            logError("EnumerationExpression {0} not found.", persistentName);
        } else {
            result = enumExpr.findLiteralByPersistentName(literalName);
            if (result == null) {
                logError("EnumerationLiteralExpression {0}#{1} not found.", persistentName, literalName);
            }
        }
        return result;
    }

    /**
     * @param typeName
     * @param cellValue
     * @return the enum value for the given typeName and the enum value given as String in the cell value;
     *         or null if the value is not found.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Object resolveJavaEnum(String typeName, Object cellValue) {
        Object result = null;
        try {
            Class<?> clazz = Class.forName(typeName);

            if (clazz.isEnum()) {
                return Enum.valueOf(((Class<Enum>) clazz), cellValue.toString());
            } else {
                logError("Type {0} is not an enum. Can not find value for {1}", typeName, cellValue.toString());
            }
        } catch (ClassNotFoundException e) {
            logError("Error setting {0} to {1}: {2} {3}", typeName, cellValue.toString(), e.getClass().getName(),
                    e.getMessage());
        } catch (SecurityException e) {
            logError("Error setting {0} to {1}: {2} {3}", typeName, cellValue.toString(), e.getClass().getName(),
                    e.getMessage());
        } catch (IllegalArgumentException e) {
            logError("Error setting {0} to {1}: {2} {3}", typeName, cellValue.toString(), e.getClass().getName(),
                    e.getMessage());
        }
        return result;
    }

    /**
     * set the given value in the given instance.
     * 
     * @param instance
     * @param propertyExpression
     * @param value
     *    value to set. If null, this method does nothing.
     * @param cell
     *    only needed for error message.
     */
    private void setValue(UniversalModelExpression instance, PropertyExpression<?> propertyExpression, Object value,
            Cell cell) {

        if (value == null) {
            return;
        }

        try {
            instance.setValue(propertyExpression, value);
        } catch (RuntimeException e) {
            logError("error setting value of cell {0}: {1} ({2})", ExcelUtils.getFullCellName(cell),
                    value.toString(), value.getClass().getName());
        }
    }

    private void logError(String format, Object... params) {
        logError(LOGGER, format, params);
    }
}