org.nuxeo.ecm.user.center.profile.UserProfileImporter.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.user.center.profile.UserProfileImporter.java

Source

/*
 * (C) Copyright 2012-2013 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * 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.
 *
 * Contributors:
 */
package org.nuxeo.ecm.user.center.profile;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.annotation.Experimental;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.api.impl.blob.FileBlob;
import org.nuxeo.ecm.core.schema.DocumentType;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.ListType;
import org.nuxeo.ecm.core.schema.types.SimpleTypeImpl;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
import org.nuxeo.ecm.core.schema.types.primitives.DateType;
import org.nuxeo.ecm.core.schema.types.primitives.DoubleType;
import org.nuxeo.ecm.core.schema.types.primitives.IntegerType;
import org.nuxeo.ecm.core.schema.types.primitives.LongType;
import org.nuxeo.ecm.core.schema.types.primitives.StringType;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.transaction.TransactionHelper;

/**
 *
 * @since 7.2
 */
@Experimental(comment = "https://jira.nuxeo.com/browse/NXP-12200")
public class UserProfileImporter {

    private static final Log log = LogFactory.getLog(UserProfileImporter.class);

    public static final String CONTENT_FILED_TYPE_NAME = "content";

    public static final String USER_PROFILE_IMPORTER_USERNAME_COL = "username";

    protected Character escapeCharacter = '\\';

    protected ImporterConfig config;

    protected String dataFileName;

    protected transient DateFormat dateformat;

    protected final Date startDate;

    protected long totalRecords = 0;

    protected long currentRecord = 0;

    public static final String BLOB_FOLDER_PROPERTY = "nuxeo.csv.blobs.folder";

    public UserProfileImporter() {
        startDate = new Date();
    }

    public void doImport(CoreSession session) {
        UserProfileService ups = Framework.getLocalService(UserProfileService.class);

        config = ups.getImporterConfig();
        if (config == null) {
            log.error("No importer configuration could be found");
            return;
        }

        dataFileName = config.getDataFileName();
        if (dataFileName == null) {
            log.error("No importer dataFileName was supplied");
            return;
        }

        InputStream is = getResourceAsStream(dataFileName);
        if (is == null) {
            log.error("Error locating CSV data file: " + dataFileName);
            return;
        }

        Reader in = new BufferedReader(new InputStreamReader(is));
        CSVParser parser = null;

        try {
            parser = CSVFormat.DEFAULT.withEscape(escapeCharacter).withHeader().parse(in);
            doImport(session, parser, ups);
        } catch (IOException e) {
            log.error("Unable to read CSV file", e);
        } finally {
            if (parser != null) {
                try {
                    parser.close();
                } catch (IOException e) {
                    log.debug(e, e);
                }
            }
        }

    }

    protected InputStream getResourceAsStream(String resource) {
        InputStream is = getClass().getClassLoader().getResourceAsStream(resource);
        if (is == null) {
            is = Framework.getResourceLoader().getResourceAsStream(resource);
            if (is == null) {
                return null;
            }
        }
        return is;
    }

    public void doImport(CoreSession session, CSVParser parser, UserProfileService userProfileService)
            throws IOException {
        log.info(String.format("Importing CSV file: %s", dataFileName));

        DocumentType docType = Framework.getLocalService(SchemaManager.class)
                .getDocumentType(UserProfileConstants.USER_PROFILE_DOCTYPE);
        if (docType == null) {
            log.error("The type " + UserProfileConstants.USER_PROFILE_DOCTYPE + " does not exist");
            return;
        }

        Map<String, Integer> header = parser.getHeaderMap();

        if (header == null) {
            // empty file?
            log.error("No header line, empty file?");
            return;
        }

        // find the index for the required name and type values
        Integer nameIndex = header.get(UserProfileImporter.USER_PROFILE_IMPORTER_USERNAME_COL);
        if (nameIndex == null) {
            log.error("Missing 'username' column");
            return;
        }

        long docsUpdatedCount = 0;
        totalRecords = parser.getRecordNumber();
        try {
            int batchSize = config.getBatchSize();
            long lineNumber = 0;

            for (CSVRecord record : parser.getRecords()) {
                lineNumber++;
                currentRecord = lineNumber;

                try {
                    if (importLine(record, lineNumber, nameIndex, docType, session, userProfileService, header)) {
                        docsUpdatedCount++;
                        if (docsUpdatedCount % batchSize == 0) {
                            commitOrRollbackTransaction();
                            startTransaction();
                        }
                    }
                } catch (NuxeoException e) {
                    // try next line
                    Throwable unwrappedException = unwrapException(e);
                    logImportError(lineNumber, "Error while importing line: %s", unwrappedException.getMessage());
                    log.debug(unwrappedException, unwrappedException);
                }
            }

            session.save();
        } finally {
            commitOrRollbackTransaction();
            startTransaction();
        }
        log.info(String.format("Done importing %s entries from CSV file: %s", docsUpdatedCount, dataFileName));
    }

    /**
     * Import a line from the CSV file.
     *
     * @param userProfileService
     * @param docType
     * @param session
     * @return {@code true} if a document has been created or updated, {@code false} otherwise.
     */
    protected boolean importLine(CSVRecord record, final long lineNumber, Integer nameIndex, DocumentType docType,
            CoreSession session, UserProfileService userProfileService, Map<String, Integer> headerValues) {
        final String name = record.get(nameIndex);
        if (StringUtils.isBlank(name)) {
            logImportError(lineNumber, "Missing 'name' value", "label.csv.importer.missingNameValue");
            return false;
        }

        Map<String, Serializable> values = computePropertiesMap(lineNumber, docType, headerValues, record);
        if (values == null) {
            // skip this line
            return false;
        }

        return updateDocument(lineNumber, name, docType, session, userProfileService, values);
    }

    protected Map<String, Serializable> computePropertiesMap(long lineNumber, DocumentType docType,
            Map<String, Integer> headerValues, CSVRecord record) {

        Map<String, Serializable> values = new HashMap<String, Serializable>();
        for (String headerValue : headerValues.keySet()) {
            String lineValue = record.get(headerValue);
            lineValue = lineValue.trim();
            String fieldName = headerValue;
            if (!UserProfileImporter.USER_PROFILE_IMPORTER_USERNAME_COL.equals(headerValue)) {
                if (!docType.hasField(fieldName)) {
                    fieldName = fieldName.split(":")[1];
                }
                if (docType.hasField(fieldName) && !StringUtils.isBlank(lineValue)) {
                    Serializable convertedValue = convertValue(docType, fieldName, headerValue, lineValue,
                            lineNumber);
                    if (convertedValue == null) {
                        return null;
                    }
                    values.put(headerValue, convertedValue);
                }
            }
        }
        return values;
    }

    protected Serializable convertValue(DocumentType docType, String fieldName, String headerValue,
            String stringValue, long lineNumber) {
        if (docType.hasField(fieldName)) {
            Field field = docType.getField(fieldName);
            if (field != null) {
                try {
                    Serializable fieldValue = null;
                    Type fieldType = field.getType();
                    if (fieldType.isComplexType()) {
                        if (fieldType.getName().equals(CONTENT_FILED_TYPE_NAME)) {
                            String blobsFolderPath = Framework.getProperty(BLOB_FOLDER_PROPERTY);
                            String path = FilenameUtils.normalize(blobsFolderPath + "/" + stringValue);
                            File file = new File(path);
                            if (file.exists()) {
                                FileBlob blob = new FileBlob(file);
                                blob.setFilename(file.getName());
                                fieldValue = blob;
                            } else {
                                logImportError(lineNumber, "The file '%s' does not exist", stringValue);
                                return null;
                            }
                        }
                        // other types not supported
                    } else {
                        if (fieldType.isListType()) {
                            Type listFieldType = ((ListType) fieldType).getFieldType();
                            if (listFieldType.isSimpleType()) {
                                /*
                                 * Array.
                                 */
                                fieldValue = stringValue.split(config.getListSeparatorRegex());
                            } else {
                                /*
                                 * Complex list.
                                 */
                                fieldValue = (Serializable) Arrays
                                        .asList(stringValue.split(config.getListSeparatorRegex()));
                            }
                        } else {
                            /*
                             * Primitive type.
                             */
                            Type type = field.getType();
                            if (type instanceof SimpleTypeImpl) {
                                type = type.getSuperType();
                            }
                            if (type.isSimpleType()) {
                                if (type instanceof StringType) {
                                    fieldValue = stringValue;
                                } else if (type instanceof IntegerType) {
                                    fieldValue = Integer.valueOf(stringValue);
                                } else if (type instanceof LongType) {
                                    fieldValue = Long.valueOf(stringValue);
                                } else if (type instanceof DoubleType) {
                                    fieldValue = Double.valueOf(stringValue);
                                } else if (type instanceof BooleanType) {
                                    fieldValue = Boolean.valueOf(stringValue);
                                } else if (type instanceof DateType) {
                                    fieldValue = getDateFormat().parse(stringValue);
                                }
                            }
                        }
                    }
                    return fieldValue;
                } catch (ParseException pe) {
                    logImportError(lineNumber, "Unable to convert field '%s' with value '%s'", headerValue,
                            stringValue);
                    log.debug(pe, pe);
                } catch (NumberFormatException nfe) {
                    logImportError(lineNumber, "Unable to convert field '%s' with value '%s'", headerValue,
                            stringValue);
                    log.debug(nfe, nfe);
                }
            }
        } else {
            logImportError(lineNumber, "Field '%s' does not exist on type '%s'", headerValue, docType.getName());
        }
        return null;
    }

    protected DateFormat getDateFormat() {
        // transient field so may become null
        if (dateformat == null) {
            dateformat = new SimpleDateFormat(config.getDateFormat());
        }
        return dateformat;
    }

    protected boolean updateDocument(long lineNumber, String name, DocumentType docType, CoreSession session,
            UserProfileService userProfileService, Map<String, Serializable> properties) {

        DocumentModel doc = userProfileService.getUserProfileDocument(name, session);
        Calendar createdDate = (Calendar) doc.getPropertyValue("dc:created");
        boolean isCreated = (createdDate.getTime().after(startDate));
        if (!isCreated && !config.isUpdateExisting()) {
            logImportInfo(lineNumber, "Document already exists for user: %s", name);
            return false;
        }

        for (Map.Entry<String, Serializable> entry : properties.entrySet()) {
            doc.setPropertyValue(entry.getKey(), entry.getValue());
        }

        try {
            session.saveDocument(doc);
        } catch (NuxeoException e) {
            Throwable unwrappedException = unwrapException(e);
            logImportError(lineNumber, "Unable to update document for user: %s: %s", name,
                    unwrappedException.getMessage());
            log.debug(unwrappedException, unwrappedException);
            return false;
        }
        return true;
    }

    /**
     * Releases the transaction resources by committing the existing transaction (if any). This is recommended before
     * running a long process.
     */
    protected void commitOrRollbackTransaction() {
        if (TransactionHelper.isTransactionActiveOrMarkedRollback()) {
            TransactionHelper.commitOrRollbackTransaction();
        }
    }

    /**
     * Starts a new transaction.
     * <p>
     * Usually called after {@code commitOrRollbackTransaction()}, for instance for saving back the results of a long
     * process.
     *
     * @return true if a new transaction was started
     */
    protected boolean startTransaction() {
        return TransactionHelper.startTransaction();
    }

    protected void logImportError(long lineNumber, String message, String... params) {
        String lineMessage = String.format("Line %d", lineNumber);
        String errorMessage = String.format(message, (Object[]) params);
        log.error(String.format("%s: %s", lineMessage, errorMessage));
    }

    protected void logImportInfo(long lineNumber, String message, String... params) {
        String lineMessage = String.format("Line %d", lineNumber);
        String infoMessage = String.format(message, (Object[]) params);
        log.info(String.format("%s: %s", lineMessage, infoMessage));
    }

    public static Throwable unwrapException(Throwable t) {
        Throwable cause = null;

        if (t != null) {
            cause = t.getCause();
        }

        if (cause == null) {
            return t;
        } else {
            return unwrapException(cause);
        }
    }

    public long getTotalRecords() {
        return totalRecords;
    }

    public long getCurrentRecord() {
        return currentRecord;
    }

}