org.silverpeas.migration.jcr.version.VersionedDocumentMigration.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.migration.jcr.version.VersionedDocumentMigration.java

Source

/*
 * Copyright (C) 2000-2013 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Writer Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have recieved a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "http://www.silverpeas.org/legal/licensing"
 *
 * 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 Affero 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/>.
 */
package org.silverpeas.migration.jcr.version;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.silverpeas.dbbuilder.sql.ConnectionFactory;
import org.silverpeas.migration.jcr.service.AttachmentException;
import org.silverpeas.migration.jcr.service.ConverterUtil;
import org.silverpeas.migration.jcr.service.RepositoryManager;
import org.silverpeas.migration.jcr.service.model.HistorisedDocument;
import org.silverpeas.migration.jcr.service.model.SimpleAttachment;
import org.silverpeas.migration.jcr.service.model.SimpleDocument;
import org.silverpeas.migration.jcr.service.model.SimpleDocumentPK;
import org.silverpeas.migration.jcr.service.repository.DocumentConverter;
import org.silverpeas.migration.jcr.service.repository.DocumentRepository;
import org.silverpeas.migration.jcr.version.model.OldDocumentMetadata;
import org.silverpeas.migration.jcr.version.model.Version;
import org.silverpeas.util.Console;
import org.silverpeas.util.DateUtil;
import org.silverpeas.util.StringUtil;

/**
 * An optimized alternative to the ComponentDocumentMigrator class for migrating in the JCR all the
 * versioned documents within a given component instance.
 *
 * @author mmoquillon
 */
class VersionedDocumentMigration implements Callable<Long> {

    public static final String SELECT_DOCUMENTS = "SELECT documentid, documentname, "
            + "documentdescription, documentstatus, documentownerid, documentcheckoutdate, documentinfo, "
            + "foreignid, instanceid, typeworklist, currentworklistorder, alertdate, expirydate, "
            + "documentordernum FROM sb_version_document WHERE instanceid = ? ORDER BY foreignid, "
            + "documentordernum";
    public static final String SELECT_DOCUMENT_VERSION = "SELECT versionid, documentid, "
            + "versionmajornumber, versionminornumber, versionauthorid, versioncreationdate, "
            + "versioncomments, versiontype,  versionstatus, versionphysicalname, versionlogicalname, "
            + "versionmimetype, versionsize, instanceid, xmlform FROM sb_version_version WHERE "
            + "documentid = ? ORDER BY versionmajornumber, versionminornumber";
    public static final String DELETE_DOCUMENT_VERSIONS = "DELETE FROM sb_version_version WHERE documentid = ?";
    public static final String DELETE_DOCUMENT = "DELETE FROM sb_version_document WHERE documentid = ?";
    private final String componentId;
    private final RepositoryManager repositoryManager;
    private final DocumentRepository documentRepository;
    private final Console console;
    private static final DocumentConverter converter = new DocumentConverter();

    VersionedDocumentMigration(String instanceId, RepositoryManager repositoryManager, Console console) {
        this.componentId = instanceId;
        this.repositoryManager = repositoryManager;
        this.documentRepository = new DocumentRepository(repositoryManager);
        this.console = console;
    }

    protected long migrateComponent() throws Exception {
        long processStart = System.currentTimeMillis();
        console.printMessage("Migrating component " + componentId);
        long migratedDocumentCount = 0;
        Session session = null;
        try {
            List<OldDocumentMetadata> documents = listAllDocuments();
            session = openJCRSession();
            for (OldDocumentMetadata document : documents) {
                migratedDocumentCount += migrateAllDocumentVersions(session, document);
            }
            if (session.hasPendingChanges()) {
                session.save();
            }
            cleanAll(documents);
        } catch (Exception ex) {
            console.printError("Error during the migration of the versioned documents in component " + componentId
                    + ": " + ex.getMessage(), ex);
            throw ex;
        } finally {
            repositoryManager.logout(session);
        }
        long processEnd = System.currentTimeMillis();
        console.printMessage("Migrating the component " + componentId + " required the migration of "
                + migratedDocumentCount + " documents in " + (processEnd - processStart) + "ms");
        console.printMessage("");

        return migratedDocumentCount;
    }

    private long migrateAllDocumentVersions(Session session, OldDocumentMetadata metadata)
            throws SQLException, ParseException, IOException {
        console.printMessage("=> Creating document for " + metadata.getTitle() + " with "
                + metadata.getHistory().size() + " versions");
        long processStart = System.currentTimeMillis();
        long migratedDocumentCount = 0L;
        HistorisedDocument document = buildHistorisedDocument(metadata);
        createDocumentNodeInJCR(session, document);
        createDocumentPermalink(document, metadata);
        for (Version version : metadata.getHistory()) {
            migrateDocumentVersion(session, document, version, metadata);
            createVersionPermalink(getDocumentVersionUUID(document, version), version.getId());
            migratedDocumentCount++;
        }
        long processEnd = System.currentTimeMillis();
        console.printMessage("   we have created  " + migratedDocumentCount + " for " + metadata.getTitle()
                + " with " + metadata.getHistory().size() + " versions in " + (processEnd - processStart) + "ms");
        return migratedDocumentCount;
    }

    private HistorisedDocument buildHistorisedDocument(OldDocumentMetadata metadata) {
        HistorisedDocument document = new HistorisedDocument();
        document.setPK(new SimpleDocumentPK(null, metadata.getInstanceId()));
        document.setAlert(metadata.getAlert());
        document.setForeignId(metadata.getForeignId());
        document.setReservation(metadata.getReservation());
        document.setExpiry(metadata.getExpiry());
        document.setOrder(metadata.getOrder());
        return document;
    }

    private SimpleAttachment buildSimpleAttachment(Version version, OldDocumentMetadata metadata)
            throws IOException {
        SimpleAttachment attachment = new SimpleAttachment(version.getFileName(), ConverterUtil.defaultLanguage,
                metadata.getTitle(), metadata.getDescription(), version.getSize(), version.getContentType(),
                version.getCreatedBy(), version.getCreation(), version.getXmlFormId());
        // link this attachment to the actual file in order to relocate it later with the migration in
        // the JCR.
        attachment.setFile(version.getAttachment());
        return attachment;
    }

    private void setVersioningAttributes(HistorisedDocument document, Version version) {
        document.setComment(version.getComment());
        document.setPublicDocument(version.isPublic());
        document.setUpdated(version.getCreation());
        document.setUpdatedBy(version.getCreatedBy());
    }

    private void createDocumentNodeInJCR(Session session, HistorisedDocument document) {
        try {
            // create the document node in the JCR
            documentRepository.createDocument(session, document);
            // save the created node(s) in the JCR
            session.save();
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    private void createVersionNodeInJCR(Session session, HistorisedDocument document) throws IOException {
        try {
            // get the location, in the filesystem, of the last version if any to duplicate the content
            // of this directory later
            File previousVersionDirectory = null;
            if (!document.getHistory().isEmpty()) {
                String path = document.getDirectoryPath(document.getLanguage());
                previousVersionDirectory = new File(path).getParentFile();
            }

            // set the last owner of the attachment to the document node, who is the author of this
            // attachment version.
            String owner = document.getEditedBy();
            if (!StringUtil.isDefined(owner)) {
                owner = document.getUpdatedBy();
            }
            // lock the document node for update. The new owner is set as a node property.
            documentRepository.lock(session, document, owner);

            // create a version node for the current attachment version. If there is no one already
            // attachment node for the attachment, then create it as a child of the document node.
            Node versionNode = documentRepository.unlock(session, document);
            document.getHistory().add(converter.convertNode(versionNode, document.getLanguage()));

            // save the change in the JCR.
            session.save();

            // now create the file at the location corresponding to the current version in the filesystem,
            copyContent(document);
            if (previousVersionDirectory != null) {
                // duplicate the content of the previous version into the location of the new one.
                String path = document.getDirectoryPath(document.getLanguage()).replace('/', File.separatorChar);
                File currentVersionDirectory = new File(path).getParentFile();
                duplicateContents(previousVersionDirectory, currentVersionDirectory);
            }

        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Migrates the specified version of the specified document in the JCR.
     *
     * @param session the current JCR session.
     * @param document the document to which the version is related.
     * @param version the version of the document to migrate.
     * @param metadata some metadata shared by all the document versions.
     * @throws IOException if an error occurs with the attachment file.
     */
    private void migrateDocumentVersion(Session session, HistorisedDocument document, Version version,
            OldDocumentMetadata metadata) throws IOException {
        SimpleAttachment attachment = buildSimpleAttachment(version, metadata);
        document.setAttachment(attachment);
        setVersioningAttributes(document, version);
        if (!StringUtil.isDefined(version.getCreatedBy())) {
            console.printWarning(
                    "We have a null id for the author of document " + document + " and version " + metadata);
        }
        createVersionNodeInJCR(session, document);
    }

    private Session openJCRSession() {
        try {
            return repositoryManager.getSession();
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    private void copyContent(SimpleDocument document) throws IOException, RepositoryException {
        InputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(document.getAttachment().getFile()));
            documentRepository.storeContent(document, in);
        } catch (FileNotFoundException ex) {
            throw new AttachmentException(ex);
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    private void duplicateContents(File source, File target) throws IOException {
        if (!source.exists() || !source.isDirectory() || source.listFiles() == null) {
            return;
        }
        if (!target.exists()) {
            target.mkdir();
        }
        for (File langDir : source.listFiles()) {
            File targetLangDir = new File(target, langDir.getName());
            if (!targetLangDir.exists()) {
                FileUtils.copyDirectory(langDir, targetLangDir);
            }
        }
    }

    private String getDocumentVersionUUID(HistorisedDocument document, Version version) {
        for (SimpleDocument doc : document.getHistory()) {
            if (doc.getMajorVersion() == version.getMajor() && doc.getMinorVersion() == version.getMinor()) {
                return doc.getId();
            }
        }
        return document.getId();
    }

    private void cleanAll(List<OldDocumentMetadata> metadata) throws SQLException {
        console.printMessage("   Clean all the deprecated documents in " + this.componentId);
        Connection connection = getConnection();
        connection.setAutoCommit(false);
        PreparedStatement deleteVersions = connection.prepareStatement(DELETE_DOCUMENT_VERSIONS);
        PreparedStatement deleteAttachment = connection.prepareStatement(DELETE_DOCUMENT);
        try {
            for (OldDocumentMetadata document : metadata) {
                try {
                    deleteVersions.setLong(1, document.getOldSilverpeasId());
                    deleteVersions.executeUpdate();
                    deleteAttachment.setLong(1, document.getOldSilverpeasId());
                    deleteAttachment.executeUpdate();
                    connection.commit();
                    deleteVersions.clearParameters();
                    deleteAttachment.clearParameters();
                } catch (SQLException ex) {
                    console.printError("Error while cleaning up in database the document " + document.getTitle()
                            + " (id = " + document.getOldSilverpeasId() + ")");
                    throw ex;
                }
                for (Version version : document.getHistory()) {
                    File file = null;
                    try {
                        file = version.getAttachment();
                        if (file != null) {
                            ConverterUtil.deleteFile(file);
                        }
                    } catch (IOException ioex) {
                        String fileName = (file != null ? file.getPath() : "");
                        console.printError("Error deleting file " + fileName, ioex);
                    }
                }
            }
        } finally {
            DbUtils.closeQuietly(deleteVersions);
            DbUtils.closeQuietly(deleteAttachment);
            DbUtils.closeQuietly(connection);
        }
    }

    @Override
    public Long call() throws Exception {
        console.printMessage("Migrating component " + componentId);
        return migrateComponent();
    }

    private List<OldDocumentMetadata> listAllDocuments() throws ParseException, IOException, SQLException {
        Connection connection = getConnection();
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<OldDocumentMetadata> documents = new ArrayList<OldDocumentMetadata>(500);
        try {
            pstmt = connection.prepareStatement(SELECT_DOCUMENTS);
            pstmt.setString(1, componentId);
            rs = pstmt.executeQuery();
            while (rs.next()) {
                OldDocumentMetadata metadata = new OldDocumentMetadata(rs.getInt("documentordernum"),
                        rs.getString("documentdescription"), DateUtil.parse(rs.getString("alertdate")),
                        DateUtil.parse(rs.getString("expirydate")), rs.getString("documentownerid"),
                        DateUtil.parse(rs.getString("documentcheckoutdate")), rs.getString("instanceid"),
                        rs.getString("foreignid"), rs.getLong("documentid"), rs.getString("documentname"));
                OldDocumentMetadata aDocument = fillWithVersion(metadata);
                if (aDocument.getHistory().isEmpty()) {
                    console.printWarning("The document " + metadata
                            + " doesn't belong to any component instance! So it is not taken into account");
                } else {
                    documents.add(aDocument);
                }
            }
        } catch (SQLException sqlex) {
            throw sqlex;
        } finally {
            DbUtils.closeQuietly(rs);
            DbUtils.closeQuietly(pstmt);
            DbUtils.closeQuietly(connection);
        }
        return documents;
    }

    private OldDocumentMetadata fillWithVersion(OldDocumentMetadata document)
            throws ParseException, IOException, SQLException {
        Connection connection = getConnection();
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = connection.prepareStatement(SELECT_DOCUMENT_VERSION);
            pstmt.setLong(1, document.getOldSilverpeasId());
            rs = pstmt.executeQuery();
            while (rs.next()) {
                Version version = new Version(rs.getInt("versionid"), rs.getInt("versionminornumber"),
                        rs.getInt("versionmajornumber"), DateUtil.parse(rs.getString("versioncreationdate")),
                        rs.getString("versionauthorid"), rs.getString("versionlogicalname"),
                        rs.getString("versionphysicalname"), rs.getString("versionmimetype"),
                        rs.getLong("versionsize"), rs.getString("xmlform"), rs.getString("versioncomments"),
                        document.getInstanceId());
                // the file related to this version
                File attachment = version.getAttachment();
                // some times, a version of the document can exist in database but references actually no
                // attachements!
                if (attachment != null && attachment.exists() && attachment.isFile() && attachment.length() > 0L) {
                    document.addVersion(version);
                } else {
                    console.printWarning("The file refered by " + version
                            + " doesn't exist in the filesystem! So, it is not taken into account");
                }
            }
        } catch (SQLException sqlex) {
            throw sqlex;
        } finally {
            DbUtils.closeQuietly(rs);
            DbUtils.closeQuietly(pstmt);
            DbUtils.closeQuietly(connection);
        }
        return document;
    }

    private void createDocumentPermalink(HistorisedDocument document, OldDocumentMetadata metadata)
            throws SQLException {
        Connection connection = getConnection();
        PreparedStatement pstmt = null;
        try {
            pstmt = connection
                    .prepareStatement("INSERT INTO permalinks_document (documentId, documentUuid) VALUES( ?, ?)");
            pstmt.setLong(1, metadata.getOldSilverpeasId());
            pstmt.setString(2, document.getId());
            pstmt.executeUpdate();
            connection.commit();
        } catch (SQLException sqlex) {
            throw sqlex;
        } finally {
            DbUtils.closeQuietly(pstmt);
            DbUtils.closeQuietly(connection);
        }
    }

    private void createVersionPermalink(String uuid, int versionId) throws SQLException {
        Connection connection = getConnection();
        PreparedStatement pstmt = null;
        try {
            pstmt = connection
                    .prepareStatement("INSERT INTO permalinks_version (versionId, versionUuid) VALUES( ?, ?)");
            pstmt.setLong(1, versionId);
            pstmt.setString(2, uuid);
            pstmt.executeUpdate();
            connection.commit();
        } catch (SQLException sqlex) {
            throw sqlex;
        } finally {
            DbUtils.closeQuietly(pstmt);
            DbUtils.closeQuietly(connection);
        }
    }

    private Connection getConnection() throws SQLException {
        return ConnectionFactory.getConnection();
    }
}