com.adobe.communities.ugc.migration.importer.ImportFileUploadServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.communities.ugc.migration.importer.ImportFileUploadServlet.java

Source

/*************************************************************************
 *
 * ADOBE SYSTEMS INCORPORATED
 * Copyright 2015 Adobe Systems Incorporated
 * All Rights Reserved.
 *
 * NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the
 * terms of the Adobe license agreement accompanying it.  If you have received this file from a
 * source other than Adobe, then your use, modification, or distribution of it requires the prior
 * written permission of Adobe.
 **************************************************************************/
package com.adobe.communities.ugc.migration.importer;

import com.adobe.communities.ugc.migration.ContentTypeDefinitions;
import com.adobe.cq.social.calendar.client.endpoints.CalendarOperations;
import com.adobe.cq.social.commons.comments.endpoints.CommentOperations;
import com.adobe.cq.social.forum.client.endpoints.ForumOperations;
import com.adobe.cq.social.journal.client.endpoints.JournalOperations;
import com.adobe.cq.social.qna.client.endpoints.QnaForumOperations;
import com.adobe.cq.social.tally.client.endpoints.TallyOperationsService;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.NonExistingResource;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Component(label = "UGC Migration File Importer", description = "Accepts a zipped archive of migration data, unzips its contents and saves in jcr tree", specVersion = "1.1")
@Service
@Properties({ @Property(name = "sling.servlet.paths", value = "/services/social/ugc/upload") })
public class ImportFileUploadServlet extends SlingAllMethodsServlet {

    private static final Logger LOG = LoggerFactory.getLogger(ImportFileUploadServlet.class);

    public final static String UPLOAD_DIR = "/etc/migration/uploadFile";

    @Reference
    private ForumOperations forumOperations;

    @Reference
    private QnaForumOperations qnaForumOperations;

    @Reference
    private CommentOperations commentOperations;

    @Reference
    private TallyOperationsService tallyOperationsService;

    @Reference
    private CalendarOperations calendarOperations;

    @Reference
    private JournalOperations journalOperations;

    @Reference
    private SocialUtils socialUtils;

    @Reference
    private ResourceResolverFactory rrf;

    /**
     * The get operation returns a JSON string representing the current contents of the file repository holding our
     * current migration files. If necessary, this method will also create the files' repository resource.
     * @param request - the request
     * @param response - the response
     * @throws ServletException
     * @throws IOException
     */
    protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
            throws ServletException, IOException {

        final ResourceResolver resolver = request.getResourceResolver();

        UGCImportHelper.checkUserPrivileges(resolver, rrf);

        final Resource parentResource = resolver.getResource(UPLOAD_DIR);
        final JSONWriter writer = new JSONWriter(response.getWriter());
        writer.setTidy(true);
        if (null == parentResource || parentResource instanceof NonExistingResource) {
            // create migration directory if needed
            final String[] dir_parts = UPLOAD_DIR.split("/");
            final StringBuilder parentPath = new StringBuilder("/");
            final Map<String, Object> folderProperties = new HashMap<String, Object>();
            folderProperties.put(JcrConstants.JCR_PRIMARYTYPE, "sling:Folder");
            for (final String dir : dir_parts) {
                if (dir.equals("")) {
                    continue;
                }
                Resource currentParent = resolver.getResource(parentPath + dir);
                if (null == currentParent || currentParent instanceof NonExistingResource) {
                    currentParent = resolver.getResource(parentPath.toString());
                    resolver.create(currentParent, dir, folderProperties);
                }
                parentPath.append(dir).append("/");
            }
            resolver.commit();
            try {
                writer.array();
                writer.endArray();
                return;
            } catch (JSONException e) {
                throw new ServletException("Unable to output JSON", e);
            }
        }

        final Iterable<Resource> folders = parentResource.getChildren();

        try {
            writer.array();
            for (final Resource folder : folders) {
                final ValueMap vm = folder.adaptTo(ValueMap.class);
                // check that the type is "sling:Folder" and that it contains the properties "uploadedFileName" and
                // "uploadDate"
                if (vm.containsKey(JcrConstants.JCR_PRIMARYTYPE)
                        && vm.get(JcrConstants.JCR_PRIMARYTYPE).equals("sling:Folder")
                        && vm.containsKey("uploadedFileName") && vm.containsKey("uploadDate")) {

                    writer.object();
                    writer.key("folderName");
                    writer.value(folder.getName());
                    writer.key("filename");
                    writer.value(vm.get("uploadedFileName"));
                    // dig into the child nodes recursively to build up the "files" array
                    writer.key("files");
                    writer.array();
                    final ArrayList<String> filenames = new ArrayList<String>();
                    extractFilenames(folder, "", filenames);
                    for (final String filename : filenames) {
                        writer.value(filename);
                    }
                    writer.endArray();
                    writer.key("uploadDate"); // record the date here to get the time our upload completed
                    writer.value(((GregorianCalendar) vm.get("uploadDate")).getTime().getTime());
                    writer.endObject();
                }
            }
            writer.endArray();
        } catch (final JSONException e) {
            throw new ServletException("Unable to output JSON", e);
        }

    }

    /**
     * Recursively populate the fileNames array
     * @param folder - the parent Resource
     * @param parentPath - the String that gives us the path we're currently within
     * @param fileNames - the ArrayList that will eventually contain all of the file names
     */
    private void extractFilenames(final Resource folder, final String parentPath,
            final ArrayList<String> fileNames) {
        Iterable<Resource> children = folder.getChildren();
        for (final Resource child : children) {
            final ValueMap childVM = child.adaptTo(ValueMap.class);
            // if there's a single child named "file" of type "nt:file", then parentPath is a file name and we're done
            if (child.getName().equals("file") && childVM.containsKey(JcrConstants.JCR_PRIMARYTYPE)
                    && childVM.get(JcrConstants.JCR_PRIMARYTYPE).equals(JcrConstants.NT_FILE)) {
                fileNames.add(parentPath);
                return;
            }
            // otherwise, keep diving into the child nodes
            extractFilenames(child, parentPath + "/" + child.getName(), fileNames);
        }
    }

    /**
     * The post operation accepts uploaded zip files, then explodes their contents and saves them into the jcr tree
     * @param request - the request
     * @param response - the response
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
            throws ServletException, IOException {

        final ResourceResolver resolver = request.getResourceResolver();

        UGCImportHelper.checkUserPrivileges(resolver, rrf);

        final Resource parentResource = resolver.getResource(UPLOAD_DIR);
        if (null == parentResource || parentResource instanceof NonExistingResource) {
            throw new ServletException("upload directory hasn't been created");
        }

        // get the uploaded file
        final RequestParameter[] fileRequestParameters = request.getRequestParameters("file");
        if (fileRequestParameters == null || fileRequestParameters.length <= 0
                || fileRequestParameters[0].isFormField()
                || !fileRequestParameters[0].getFileName().endsWith(".zip")) {

            throw new ServletException("Unrecognized file input type");
        }
        final String filename = fileRequestParameters[0].getFileName();
        // create the folder name for the uploaded file contents

        final Random RNG = new Random();
        final String randomString = String.valueOf(RNG.nextInt(Integer.MAX_VALUE))
                + String.valueOf(RNG.nextInt(Integer.MAX_VALUE));

        // record the folder name for sending back with the response
        final JSONWriter writer = new JSONWriter(response.getWriter());
        writer.setTidy(true);
        final Calendar uploadDate = new GregorianCalendar();
        try {
            writer.object();
            writer.key("folderName");
            writer.value(randomString);
            writer.key("filename");
            writer.value(filename);
            writer.key("uploadDate");
            writer.value(uploadDate.getTime().getTime()); // <-- not a typo
            writer.key("files");
            writer.array();
        } catch (JSONException e) {
            throw new ServletException("Unable to use JSONWriter", e);
        }
        // actually create the folder
        final Map<String, Object> folderProperties = new HashMap<String, Object>();
        folderProperties.put(JcrConstants.JCR_PRIMARYTYPE, "sling:Folder");
        folderProperties.put("uploadedFileName", filename);
        folderProperties.put("uploadDate", uploadDate);
        final Resource folder = resolver.create(parentResource, randomString, folderProperties);

        // prepare to read the uploaded zip file
        final InputStream uploadedFileInputStream;
        try {
            uploadedFileInputStream = fileRequestParameters[0].getInputStream();
        } catch (IOException e) {
            throw new ServletException("Could not read zip archive");
        }
        final ZipInputStream zipInputStream = new ZipInputStream(uploadedFileInputStream);

        try {
            saveExplodedFiles(resolver, folder, writer, zipInputStream, request.getParameter("basePath"));
            // close our JSONWriter
            writer.endArray();
            writer.endObject();
        } catch (final JSONException e) {
            throw new ServletException("Unable to close JSONWriter", e);
        } catch (final ServletException e) {
            resolver.delete(folder); // clean up after ourselves
            throw e;
        } finally {
            // close input streams
            zipInputStream.close();
            uploadedFileInputStream.close();
        }
    }

    private void saveExplodedFiles(final ResourceResolver resolver, final Resource folder, final JSONWriter writer,
            final ZipInputStream zipInputStream, final String basePath) throws ServletException {

        // we need the closeShieldInputStream to prevent the zipInputStream from being closed during resolver.create()
        final CloseShieldInputStream closeShieldInputStream = new CloseShieldInputStream(zipInputStream);

        // fileResourceProperties and folderProperties will be reused inside the while loop
        final Map<String, Object> fileResourceProperties = new HashMap<String, Object>();
        fileResourceProperties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE);

        final Map<String, Object> folderProperties = new HashMap<String, Object>();
        folderProperties.put(JcrConstants.JCR_PRIMARYTYPE, "sling:Folder");

        ZipEntry zipEntry;
        try {
            zipEntry = zipInputStream.getNextEntry();
        } catch (final IOException e) {
            throw new ServletException("Unable to read entries from uploaded zip archive", e);
        }
        final List<Resource> toDelete = new ArrayList<Resource>();
        while (zipEntry != null) {
            // store files under the provided folder
            try {
                final String name = ResourceUtil.normalize("/" + zipEntry.getName());
                if (null == name) {
                    // normalize filename and if they aren't inside upload path, don't store them
                    continue;
                }
                Resource parent = folder;
                Resource subFolder = null;
                for (final String subFolderName : name.split("/")) {
                    // check if the sub-folder already exists
                    subFolder = resolver.getResource(parent, subFolderName);
                    if (null == subFolder || subFolder instanceof NonExistingResource) {
                        // create the sub-folder
                        subFolder = resolver.create(parent, subFolderName, folderProperties);
                    }
                    parent = subFolder;
                }
                if (!zipEntry.isDirectory()) {
                    // first represent the file as a resource
                    final Resource file = resolver.create(subFolder, "file", fileResourceProperties);
                    // now store its data as a jcr:content node
                    final Map<String, Object> fileProperties = new HashMap<String, Object>();
                    byte[] bytes = IOUtils.toByteArray(closeShieldInputStream);
                    fileProperties.put(JcrConstants.JCR_DATA, new String(bytes, "UTF8"));
                    fileProperties.put(JcrConstants.JCR_PRIMARYTYPE, "nt:resource");
                    resolver.create(file, JcrConstants.JCR_CONTENT, fileProperties);

                    // if provided a basePath, import immediately
                    if (StringUtils.isNotBlank(basePath) && null != file
                            && !(file instanceof NonExistingResource)) {
                        Resource fileContent = file.getChild(JcrConstants.JCR_CONTENT);
                        if (null != fileContent && !(fileContent instanceof NonExistingResource)) {
                            final ValueMap contentVM = fileContent.getValueMap();
                            InputStream inputStream = (InputStream) contentVM.get(JcrConstants.JCR_DATA);
                            if (inputStream != null) {
                                final JsonParser jsonParser = new JsonFactory().createParser(inputStream);
                                jsonParser.nextToken(); // get the first token
                                String resName = basePath + name.substring(0, name.lastIndexOf(".json"));
                                Resource resource = resolver.getResource(resName);
                                if (resource == null) {
                                    // voting does not have a node under articles
                                    resource = resolver.getResource(resName.substring(0, resName.lastIndexOf("/")));
                                }
                                try {
                                    importFile(jsonParser, resource, resolver);
                                    toDelete.add(file);
                                } catch (final Exception e) {
                                    // add the file name to our response ONLY if we failed to import it
                                    writer.value(name);
                                    // we want to log the reason we weren't able to import, but don't stop importing
                                    LOG.error(e.getMessage());
                                }
                            }
                        }
                    } else if (StringUtils.isBlank(basePath) && null != file
                            && !(file instanceof NonExistingResource)) {
                        // add the file name to our response
                        writer.value(name);
                    }
                }
                resolver.commit();
                zipEntry = zipInputStream.getNextEntry();
            } catch (final IOException e) {
                // convert any IOExceptions into ServletExceptions
                throw new ServletException(e.getMessage(), e);
            } catch (final JSONException e) {
                // convert any JSONExceptions into ServletExceptions
                throw new ServletException(e.getMessage(), e);
            }
        }
        closeShieldInputStream.close();
        // delete any files that were successfully imported
        if (!toDelete.isEmpty()) {
            for (final Resource deleteResource : toDelete) {
                deleteResource(deleteResource);
            }
        }
    }

    protected void doDelete(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
            throws ServletException, IOException {

        final ResourceResolver resolver = request.getResourceResolver();
        UGCImportHelper.checkUserPrivileges(resolver, rrf);

        final RequestParameter filePathParam = request.getRequestParameter("filePath");
        if (null == filePathParam) {
            throw new ServletException("Required parameter 'filePath' missing");
        }
        final String filePath = filePathParam.getString();
        if (!filePath.startsWith(UPLOAD_DIR)) {
            throw new ServletException("Path to file resource lies outside migration import path");
        }
        final Resource fileResource = resolver.getResource(filePath);
        if (null != fileResource && !(fileResource instanceof NonExistingResource)) {
            deleteResource(fileResource);
        }
    }

    /**
     * Delete the file after a successful migration, including any newly-empty parent directories
     * @param fileResource the Resource being deleted
     */
    public static void deleteResource(final Resource fileResource) throws ServletException {
        // do a quick check that we're deleting a resource we're allowed to delete with this method
        final String path = fileResource.getPath();
        if (!path.startsWith(UPLOAD_DIR)) {
            throw new ServletException("Cannot delete resource outside of designated upload folder");
        }
        final Resource parent = fileResource.getParent();

        if (parent.getPath().equals(UPLOAD_DIR)) {
            // don't bother checking for siblings, we won't be going any higher to delete
            try {
                final ResourceResolver resolver = fileResource.getResourceResolver();
                resolver.delete(fileResource);
                resolver.commit();
            } catch (final PersistenceException e) {
                throw new ServletException("Failed to delete a file resource following migration", e);
            }
        } else {
            // Check to see if the resource we want to delete has siblings. If it doesn't, go one level higher and
            // check
            // again. If it does have siblings, then just delete the current resource rather than the parent.
            final Iterable<Resource> siblings = parent.getChildren();
            int count = 0;
            boolean deleteParent = true;
            for (final Resource sibling : siblings) {
                count++;
                if (count > 1) {
                    deleteParent = false; // there's at least one thing besides the resource we mean to delete
                }
            }
            if (deleteParent) {
                deleteResource(parent);
            } else {
                try {
                    final ResourceResolver resolver = fileResource.getResourceResolver();
                    resolver.delete(fileResource);
                    resolver.commit();
                } catch (final PersistenceException e) {
                    throw new ServletException("Failed to delete a file resource following migration", e);
                }
            }
        }
    }

    protected void doPut(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
            throws ServletException, IOException {

        final String path = request.getRequestParameter("path").getString();
        final ResourceResolver resolver = request.getResourceResolver();
        UGCImportHelper.checkUserPrivileges(resolver, rrf);

        final Resource resource = resolver.getResource(path);
        if (resource == null) {
            throw new ServletException("Could not find a valid resource for import");
        }
        final String filePath = request.getRequestParameter("filePath").getString();
        if (!filePath.startsWith(ImportFileUploadServlet.UPLOAD_DIR)) {
            throw new ServletException("Path to file resource lies outside migration import path");
        }
        final Resource fileResource = resolver.getResource(filePath);
        if (fileResource == null) {
            throw new ServletException("Could not find a valid file resource to read");
        }
        // get the input stream from the file resource
        Resource file = fileResource.getChild("file");
        if (null != file && !(file instanceof NonExistingResource)) {
            file = file.getChild(JcrConstants.JCR_CONTENT);
            if (null != file && !(file instanceof NonExistingResource)) {
                final ValueMap contentVM = file.getValueMap();
                InputStream inputStream = (InputStream) contentVM.get(JcrConstants.JCR_DATA);
                if (inputStream != null) {
                    final JsonParser jsonParser = new JsonFactory().createParser(inputStream);
                    jsonParser.nextToken(); // get the first token

                    importFile(jsonParser, resource, resolver);
                    deleteResource(fileResource);
                    return;
                }
            }
        }
        throw new ServletException("Unable to read file in provided file resource path");
    }

    /**
     * Handle each of the importable types of ugc content
     * @param jsonParser - the parsing stream
     * @param resource - the parent resource of whatever it is we're importing (must already exist)
     * @throws ServletException
     * @throws IOException
     */
    private void importFile(final JsonParser jsonParser, final Resource resource, final ResourceResolver resolver)
            throws ServletException, IOException {
        final UGCImportHelper importHelper = new UGCImportHelper();
        JsonToken token1 = jsonParser.getCurrentToken();
        if (token1.equals(JsonToken.START_OBJECT)) {
            jsonParser.nextToken();
            if (jsonParser.getCurrentName().equals(ContentTypeDefinitions.LABEL_CONTENT_TYPE)) {
                jsonParser.nextToken();
                final String contentType = jsonParser.getValueAsString();
                if (contentType.equals(ContentTypeDefinitions.LABEL_QNA_FORUM)) {
                    importHelper.setQnaForumOperations(qnaForumOperations);
                } else if (contentType.equals(ContentTypeDefinitions.LABEL_FORUM)) {
                    importHelper.setForumOperations(forumOperations);
                } else if (contentType.equals(ContentTypeDefinitions.LABEL_COMMENTS)) {
                    importHelper.setCommentOperations(commentOperations);
                } else if (contentType.equals(ContentTypeDefinitions.LABEL_CALENDAR)) {
                    importHelper.setCalendarOperations(calendarOperations);
                } else if (contentType.equals(ContentTypeDefinitions.LABEL_JOURNAL)) {
                    importHelper.setJournalOperations(journalOperations);
                } else if (contentType.equals(ContentTypeDefinitions.LABEL_TALLY)) {
                    importHelper.setSocialUtils(socialUtils);
                }
                importHelper.setTallyService(tallyOperationsService); // (everything potentially needs tally)
                jsonParser.nextToken(); // content
                if (jsonParser.getCurrentName().equals(ContentTypeDefinitions.LABEL_CONTENT)) {
                    jsonParser.nextToken();
                    token1 = jsonParser.getCurrentToken();
                    if (token1.equals(JsonToken.START_OBJECT) || token1.equals(JsonToken.START_ARRAY)) {
                        if (!resolver.isLive()) {
                            throw new ServletException("Resolver is already closed");
                        }
                    } else {
                        throw new ServletException("Start object token not found for content");
                    }
                    if (token1.equals(JsonToken.START_OBJECT)) {
                        try {
                            if (contentType.equals(ContentTypeDefinitions.LABEL_QNA_FORUM)) {
                                importHelper.importQnaContent(jsonParser, resource, resolver);
                            } else if (contentType.equals(ContentTypeDefinitions.LABEL_FORUM)) {
                                importHelper.importForumContent(jsonParser, resource, resolver);
                            } else if (contentType.equals(ContentTypeDefinitions.LABEL_COMMENTS)) {
                                importHelper.importCommentsContent(jsonParser, resource, resolver);
                            } else if (contentType.equals(ContentTypeDefinitions.LABEL_JOURNAL)) {
                                importHelper.importJournalContent(jsonParser, resource, resolver);
                            } else {
                                LOG.info("Unsupported content type: {}", contentType);
                                jsonParser.skipChildren();
                            }
                            jsonParser.nextToken();
                        } catch (final IOException e) {
                            throw new ServletException(e);
                        }
                        jsonParser.nextToken(); // skip over END_OBJECT
                    } else {
                        try {
                            if (contentType.equals(ContentTypeDefinitions.LABEL_CALENDAR)) {
                                importHelper.importCalendarContent(jsonParser, resource);
                            } else if (contentType.equals(ContentTypeDefinitions.LABEL_TALLY)) {
                                importHelper.importTallyContent(jsonParser, resource);
                            } else {
                                LOG.info("Unsupported content type: {}", contentType);
                                jsonParser.skipChildren();
                            }
                            jsonParser.nextToken();
                        } catch (final IOException e) {
                            throw new ServletException(e);
                        }
                        jsonParser.nextToken(); // skip over END_ARRAY
                    }
                } else {
                    throw new ServletException("Content not found");
                }
            } else {
                throw new ServletException("No content type specified");
            }
        } else {
            throw new ServletException("Invalid Json format");
        }
    }
}