de.ks.idnadrev.expimp.xls.SingleSheetImport.java Source code

Java tutorial

Introduction

Here is the source code for de.ks.idnadrev.expimp.xls.SingleSheetImport.java

Source

/*
 * Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
 * 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 de.ks.idnadrev.expimp.xls;

import de.ks.idnadrev.expimp.DependencyGraph;
import de.ks.idnadrev.expimp.xls.result.XlsxImportSheetResult;
import de.ks.idnadrev.expimp.xls.sheet.ImportSheetHandler;
import de.ks.idnadrev.expimp.xls.sheet.ImportValue;
import de.ks.persistence.PersistentWork;
import de.ks.persistence.entity.IdentifyableEntity;
import de.ks.reflection.ReflectionUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class SingleSheetImport implements Callable<Void> {
    private static final Logger log = LoggerFactory.getLogger(SingleSheetImport.class);

    protected final ColumnProvider columnProvider;
    private final Class<?> clazz;
    private final InputStream sheetStream;
    private final DependencyGraph graph;
    private final XlsxImportSheetResult result;
    private final XlsImportCfg importCfg;
    private final EntityType<?> entityType;
    private final XSSFReader reader;

    protected final LinkedList<Runnable> runAfterImport = new LinkedList<>();

    public SingleSheetImport(Class<?> clazz, InputStream sheetStream, DependencyGraph graph, XSSFReader reader,
            XlsxImportSheetResult result, XlsImportCfg importCfg) {
        this.clazz = clazz;
        this.sheetStream = sheetStream;
        this.graph = graph;
        this.result = result;
        this.importCfg = importCfg;
        this.entityType = graph.getEntityType(clazz);
        this.reader = reader;
        columnProvider = new ColumnProvider(graph);
    }

    @Override
    public Void call() throws Exception {
        try {
            XMLReader parser = XMLReaderFactory.createXMLReader();
            ImportSheetHandler importSheetHandler = new ImportSheetHandler(clazz, reader.getSharedStringsTable(),
                    columnProvider, this::importEntity);
            parser.setContentHandler(importSheetHandler);

            InputSource inputSource = new InputSource(sheetStream);
            parser.parse(inputSource);

        } catch (SAXException | IOException | InvalidFormatException e) {
            result.generalError("Failed to parse sheet " + clazz.getName(), e);
            throw new RuntimeException(e);
        } finally {
            try {
                sheetStream.close();
            } catch (IOException e) {
                result.generalError("Could not close sheet stream " + clazz.getName(), e);
                throw new RuntimeException(e);
            }
            return null;
        }
    }

    public void importEntity(List<ImportValue> importValues) {
        if (importValues.isEmpty()) {
            return;
        }

        Object idProperty;
        if (IdentifyableEntity.class.isAssignableFrom(clazz)) {
            String idPropertyName = graph.getIdentifierProperty(clazz);
            Optional<ImportValue> first = importValues.stream()
                    .filter(v -> v.getColumnDef().getIdentifier().equals(idPropertyName)).findFirst();
            idProperty = first.get().getValue();
        } else {
            idProperty = null;
        }

        PersistentWork.run(em -> {
            Object instance;
            boolean keepExisting = importCfg.isKeepExisting();
            boolean exists;

            if (idProperty != null) {
                String idPropertyName = graph.getIdentifierProperty(clazz);

                @SuppressWarnings("unchecked")
                IdentifyableEntity loaded = PersistentWork.findByIdentification((Class<IdentifyableEntity>) clazz,
                        idPropertyName, idProperty);
                if (loaded != null) {
                    instance = loaded;
                    exists = true;
                } else {
                    exists = false;
                    instance = ReflectionUtil.newInstance(clazz);
                }
            } else {
                exists = false;
                instance = ReflectionUtil.newInstance(clazz);
            }
            if (exists && keepExisting) {
                result.success("Ignored " + clazz.getName() + " with identifier " + idProperty,
                        importValues.get(0).getCellId());
                return;
            }

            List<ToOneRelationAssignment> manadatoryRelations = getManadatoryRelations(importValues, instance);
            List<ToOneRelationAssignment> optionalRelations = getOptionalRelations(importValues);
            List<ToManyRelationAssignment> toManyRelations = getToManyRelations(importValues);
            try {
                manadatoryRelations.forEach(r -> {
                    r.run();
                });
                importValues.forEach(v -> {
                    XlsxColumn columnDef = v.getColumnDef();
                    Object value = v.getValue();
                    columnDef.setValue(instance, value);
                });
                optionalRelations.forEach(r -> {
                    r.ownerResolver = () -> {
                        Object identifier = getIdentifier(instance);
                        String identifierProperty = graph.getIdentifierProperty(clazz);
                        return resolveEntity(clazz, identifierProperty, identifier);
                    };
                });

                if (!exists) {
                    em.persist(instance);
                }
                result.success(instance.toString(), importValues.get(0).getCellId());
                toManyRelations.forEach(r -> {
                    r.ownerResolver = () -> {
                        Object identifier = getIdentifier(instance);
                        String identifierProperty = graph.getIdentifierProperty(instance.getClass());
                        return resolveEntity(clazz, identifierProperty, identifier);
                    };
                });
                runAfterImport.addAll(optionalRelations);
                runAfterImport.addAll(toManyRelations);

            } catch (Exception e) {
                result.error("Could not persist entity " + instance, e, importValues.get(0).getCellId());
                throw e;
            }
        });

    }

    private List<ToOneRelationAssignment> getManadatoryRelations(List<ImportValue> importValues, Object instance) {
        List<SingularAttribute<?, ?>> mandatoryRelations = entityType.getSingularAttributes().stream()
                .filter(a -> a.isAssociation() && !a.isOptional()).collect(Collectors.toList());

        return mandatoryRelations.stream().map(r -> {
            Optional<ImportValue> found = importValues.stream()
                    .filter(v -> v.getColumnDef().getIdentifier().equals(r.getName())).findFirst();
            if (!found.isPresent()) {
                log.warn("Could not import {} no column definition for {}.{} found. Values: {}", clazz.getName(),
                        r.getJavaType().getName(), r.getName(), importValues);
                result.warn(
                        "Could not import " + clazz.getName() + " no column definition for "
                                + r.getJavaType().getName() + "." + r.getName() + " found. Values: " + importValues,
                        null);
                return null;
            }
            ImportValue importValue = found.get();
            importValues.remove(importValue);

            String identifierProperty = graph.getIdentifierProperty(r.getJavaType());
            Consumer<Object> resultWriter = o -> result.warn("could not find association of '" + o + "' via '"
                    + r.getName() + "' to '" + importValue.getValue() + "'", importValue.getCellId());
            return new ToOneRelationAssignment(resultWriter, () -> instance, r, importValue.getColumnDef(),
                    identifierProperty, importValue.getValue());
        }).collect(Collectors.toList());
    }

    private List<ToOneRelationAssignment> getOptionalRelations(List<ImportValue> importValues) {
        List<SingularAttribute<?, ?>> optionalRelations = entityType.getSingularAttributes().stream()
                .filter(a -> a.isAssociation() && a.isOptional()).collect(Collectors.toList());

        return optionalRelations.stream().map(r -> {
            Optional<ImportValue> found = importValues.stream()
                    .filter(v -> v.getColumnDef().getIdentifier().equals(r.getName())).findFirst();
            if (!found.isPresent()) {
                return null;
            }
            ImportValue importValue = found.get();
            importValues.remove(importValue);
            Consumer<Object> resultWriter = o -> result.warn("could not find association of '" + o + "' via '"
                    + r.getName() + "' to '" + importValue.getValue() + "'", importValue.getCellId());
            String identifierProperty = graph.getIdentifierProperty(r.getJavaType());
            return new ToOneRelationAssignment(resultWriter, null, r, importValue.getColumnDef(),
                    identifierProperty, importValue.getValue());
        }).filter(r -> r != null).collect(Collectors.toList());
    }

    private List<ToManyRelationAssignment> getToManyRelations(List<ImportValue> importValues) {
        List<PluralAttribute<?, ?, ?>> pluralAttributes = entityType.getPluralAttributes().stream()
                .filter(a -> a.isAssociation()).collect(Collectors.toList());

        return pluralAttributes.stream().map(r -> {
            Optional<ImportValue> found = importValues.stream()
                    .filter(v -> v.getColumnDef().getIdentifier().equals(r.getName())).findFirst();
            if (!found.isPresent()) {
                return null;
            }
            ImportValue importValue = found.get();
            importValues.remove(importValue);
            String identifierProperty = graph.getIdentifierProperty(r.getElementType().getJavaType());
            return new ToManyRelationAssignment(importValue.getColumnDef(), r, identifierProperty,
                    (String) importValue.getValue());
        }).filter(r -> r != null).collect(Collectors.toList());
    }

    static Object getIdentifier(Object instance) {
        if (IdentifyableEntity.class.isAssignableFrom(instance.getClass())) {
            return ((IdentifyableEntity) instance).getIdValue();
        }
        return null;
    }

    static Object resolveEntity(Class<?> type, String identifierProperty, Object identifier) {
        if (IdentifyableEntity.class.isAssignableFrom(type)) {
            @SuppressWarnings("unchecked")
            IdentifyableEntity identifyableEntity = PersistentWork
                    .findByIdentification((Class<IdentifyableEntity>) type, identifierProperty, identifier);
            return identifyableEntity;
        }
        return null;
    }

    static List<Object> resolveToManyRelation(Class<?> type, String identifierProperty,
            List<String> singleIdentifiers) {
        LinkedList<Object> retval = new LinkedList<>();
        for (String identifier : singleIdentifiers) {
            Object entity = resolveEntity(type, identifierProperty, identifier);
            retval.add(entity);
        }
        return retval;
    }

    static class ToOneRelationAssignment implements Runnable {
        private final Consumer<Object> resultWriter;
        XlsxColumn ownerColumn;

        Supplier<Object> ownerResolver;
        Supplier<Object> relationResolver;

        ToOneRelationAssignment(Consumer<Object> resultWriter, Supplier<Object> ownerResolver,
                SingularAttribute<?, ?> relation, XlsxColumn ownerColumn, String identifierPropertyName,
                Object relationIdentifier) {
            this.resultWriter = resultWriter;
            this.ownerColumn = ownerColumn;
            this.ownerResolver = ownerResolver;
            relationResolver = () -> resolveEntity(relation.getJavaType(), identifierPropertyName,
                    relationIdentifier);
        }

        @Override
        public void run() {
            PersistentWork.wrap(() -> {
                Object owner = ownerResolver.get();
                Object value = relationResolver.get();
                if (value == null) {
                    resultWriter.accept(owner);
                } else {
                    ownerColumn.setValue(owner, value);
                }
            });
        }
    }

    static class ToManyRelationAssignment implements Runnable {
        XlsxColumn ownerColumn;

        Supplier<Object> ownerResolver;
        Supplier<List<Object>> relationResolver;

        ToManyRelationAssignment(XlsxColumn ownerColumn, PluralAttribute relation, String identifierPropertyName,
                String relationString) {
            this.ownerColumn = ownerColumn;

            String[] split = StringUtils.split(relationString, "|");
            ArrayList<String> singleIdentifiers = new ArrayList<>(split.length);
            for (String string : split) {
                String id = StringUtils.replace(string, ToManyColumn.SEPARATOR, ToManyColumn.SEPARATOR_REPLACEMENT);
                singleIdentifiers.add(id);
            }
            relationResolver = () -> resolveToManyRelation(relation.getElementType().getJavaType(),
                    identifierPropertyName, singleIdentifiers);
        }

        @Override
        @SuppressWarnings("unchecked")
        public void run() {
            PersistentWork.wrap(() -> {
                Object owner = ownerResolver.get();
                List<Object> values = relationResolver.get();

                Collection original = (Collection) ReflectionUtil.getFieldValue(owner, ownerColumn.getIdentifier());
                original.addAll(values);
            });
        }
    }

    public LinkedList<Runnable> getRunAfterImport() {
        return runAfterImport;
    }
}