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

Java tutorial

Introduction

Here is the source code for com.adobe.communities.ugc.migration.importer.MessagesImportServlet.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 java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
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.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.jcr.RepositoryException;
import javax.servlet.ServletException;

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.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.ModifiableValueMap;
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.servlets.SlingAllMethodsServlet;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.communities.ugc.migration.ContentTypeDefinitions;
import com.adobe.cq.social.messaging.api.Message;
import com.adobe.cq.social.messaging.api.MessageFilter;
import com.adobe.cq.social.messaging.api.MessagingService;
import com.adobe.cq.social.messaging.client.endpoints.MessagingOperationsService;
import com.adobe.cq.social.scf.ClientUtilityFactory;
import com.adobe.cq.social.scf.OperationException;
import com.adobe.cq.social.ugc.api.ValueConstraint;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.adobe.cq.social.ugcbase.core.attachments.FileDataSource;
import com.adobe.granite.security.user.UserPropertiesService;
import com.adobe.granite.xss.XSSAPI;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

@Component(label = "UGC Migration Messages Importer", description = "Accepts a zipped archive of message data, unzips its contents and saves mail messages", specVersion = "1.1")
@Service
@Properties({ @Property(name = "sling.servlet.paths", value = "/services/social/messages/import") })
public class MessagesImportServlet extends SlingAllMethodsServlet {

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

    private MessagingServiceTracker messagingServiceTracker;

    @Reference
    private MessagingOperationsService messagingService;

    @Reference
    private ResourceResolverFactory rrf;

    @Reference
    private SocialUtils socialUtils;
    @Reference
    private XSSAPI xss;
    @Reference
    private ClientUtilityFactory clientUtilsFactory;

    @Reference
    private UserPropertiesService userPropertiesService;

    @Reference
    MessagingService messageSearch;

    static final String SERVICE_SELECTOR_PROPERTY = "serviceSelector";

    protected void activate(final ComponentContext context) {
        messagingServiceTracker = new MessagingServiceTracker(context.getBundleContext());
        messagingServiceTracker.open();
    }

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

        final ResourceResolver resolver = request.getResourceResolver();

        UGCImportHelper.checkUserPrivileges(resolver, rrf);

        // first check to see if the caller provided an explicit selector for our MessagingOperationsService
        final RequestParameter serviceSelector = request.getRequestParameter("serviceSelector");
        // if no serviceSelector parameter was provided, we'll try going with whatever we get through the Reference
        if (null != serviceSelector && !serviceSelector.getString().equals("")) {
            // make sure the messagingServiceTracker was created by the activate method
            if (null == messagingServiceTracker) {
                throw new ServletException("Cannot use messagingServiceTracker");
            }
            // search for the MessagingOperationsService corresponding to the provided selector
            messagingService = messagingServiceTracker.getService(serviceSelector.getString());
            if (null == messagingService) {
                throw new ServletException(
                        "MessagingOperationsService for selector " + serviceSelector.getString() + "was not found");
            }
        }
        // finally get the uploaded file
        final RequestParameter[] fileRequestParameters = request.getRequestParameters("file");
        if (fileRequestParameters != null && fileRequestParameters.length > 0
                && !fileRequestParameters[0].isFormField()) {

            final Map<String, Object> messageModifiers = new HashMap<String, Object>();
            if (fileRequestParameters[0].getFileName().endsWith(".json")) {
                // if upload is a single json file...
                final InputStream inputStream = fileRequestParameters[0].getInputStream();
                final JsonParser jsonParser = new JsonFactory().createParser(inputStream);
                jsonParser.nextToken(); // get the first token
                importMessages(request, jsonParser, messageModifiers);
            } else if (fileRequestParameters[0].getFileName().endsWith(".zip")) {
                ZipInputStream zipInputStream;
                try {
                    zipInputStream = new ZipInputStream(fileRequestParameters[0].getInputStream());
                } catch (IOException e) {
                    throw new ServletException("Could not open zip archive");
                }
                ZipEntry zipEntry = zipInputStream.getNextEntry();
                while (zipEntry != null) {
                    final JsonParser jsonParser = new JsonFactory().createParser(zipInputStream);
                    jsonParser.nextToken(); // get the first token
                    importMessages(request, jsonParser, messageModifiers);
                    zipInputStream.closeEntry();
                    zipEntry = zipInputStream.getNextEntry();
                }
                zipInputStream.close();
            } else {
                throw new ServletException("Unrecognized file input type");
            }
            if (!messageModifiers.isEmpty()) {
                try {
                    Thread.sleep(3000); //wait 3 seconds to allow the messages to be indexed by solr for search
                } catch (final InterruptedException e) {
                    // do nothing.
                }
                updateMessageDetails(request, messageModifiers);
            }
        } else {
            throw new ServletException("No file provided for UGC data");
        }
    }

    private void importMessages(final SlingHttpServletRequest request, final JsonParser jsonParser,
            final Map<String, Object> messageModifiers) throws ServletException {

        if (!jsonParser.getCurrentToken().equals(JsonToken.START_ARRAY)) {
            throw new ServletException("unexpected starting token " + jsonParser.getCurrentToken().asString());
        }

        try {
            jsonParser.nextToken(); //presumably, we will advance to a "start object" token
            while (!jsonParser.getCurrentToken().equals(JsonToken.END_ARRAY)) {
                final Map<String, Map<String, Boolean>> recipientModifiers = new HashMap<String, Map<String, Boolean>>();
                final Map<String, Object> props = new HashMap<String, Object>();
                final Map<String, Object> messageModifier = new HashMap<String, Object>();
                List<FileDataSource> attachments = new ArrayList<FileDataSource>();
                String sender = "";
                jsonParser.nextToken(); //field name
                while (!jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
                    final String fieldName = jsonParser.getCurrentName();
                    jsonParser.nextToken(); //value
                    if (fieldName.equals("senderId")) {
                        sender = URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8");
                    } else if (fieldName.equals("added")) {
                        final Calendar calendar = new GregorianCalendar();
                        calendar.setTimeInMillis(jsonParser.getLongValue());
                        messageModifier.put("added", calendar);
                    } else if (fieldName.equals("recipients")) {
                        // build the string for the "to" property and also create the modifiers we'll need later
                        final StringBuilder recipientString = new StringBuilder();
                        //iterate over each key (each being a recipient id)
                        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
                            jsonParser.nextToken(); // should get first recipientId
                            while (!jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
                                final String recipientId = jsonParser.getCurrentName();
                                jsonParser.nextToken(); //start object
                                jsonParser.nextToken(); //first label
                                final Map<String, Boolean> interactionModifiers = new HashMap<String, Boolean>();
                                while (!jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
                                    final String label = jsonParser.getCurrentName();
                                    jsonParser.nextToken();
                                    final Boolean labelValue = jsonParser.getBooleanValue();
                                    interactionModifiers.put(label, labelValue);
                                    jsonParser.nextToken(); //next label or end object
                                }
                                try {
                                    final String userPath = userPropertiesService.getAuthorizablePath(recipientId);
                                    recipientModifiers.put(userPath, interactionModifiers);
                                    recipientString.append(recipientId);
                                } catch (final RepositoryException e) {
                                    // log the fact that a recipient specified in the json file doesn't exist in this
                                    // environment
                                    throw new ServletException(
                                            "A recipient specified in the migration file couldn't "
                                                    + "be found in this environment",
                                            e);
                                }
                                jsonParser.nextToken(); // next recipientId or end object
                                if (jsonParser.getCurrentToken().equals(JsonToken.FIELD_NAME)) {
                                    recipientString.append(';');
                                }
                            }
                            props.put("to", recipientString);
                            messageModifier.put("recipientDetails", recipientModifiers);
                        }
                    } else if (fieldName.equals(ContentTypeDefinitions.LABEL_ATTACHMENTS)) {
                        UGCImportHelper.getAttachments(jsonParser, attachments);
                    } else {
                        props.put(fieldName, URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8"));
                    }
                    jsonParser.nextToken(); //either next field name or end object
                }
                final Random range = new Random();
                final String key = String.valueOf(range.nextInt(Integer.MAX_VALUE))
                        + String.valueOf(range.nextInt(Integer.MAX_VALUE));
                // we're going to temporarily overwrite the subject (to do a search) and need to track its initial value
                if (props.containsKey("subject")) {
                    messageModifier.put("subject", props.get("subject"));
                } else {
                    messageModifier.put("subject", "");
                }
                props.put("subject", key); //use subject as the search key
                messageModifiers.put(key, messageModifier);
                try {
                    short result = messagingService.create(request.getResourceResolver(), request.getResource(),
                            sender, props, attachments,
                            clientUtilsFactory.getClientUtilities(xss, request, socialUtils));

                    if (result != 200) {
                        throw new ServletException("Message sending failed. Return code was " + result);
                    }
                } catch (final OperationException e) {
                    throw new ServletException("Unable to create a message through the operation service", e);
                }
                jsonParser.nextToken(); //either END_ARRAY or START_OBJECT
            }

        } catch (final IOException e) {
            throw new ServletException("Encountered exception while parsing json content", e);
        }
    }

    private void updateMessageDetails(final SlingHttpServletRequest request,
            final Map<String, Object> messageModifiers) throws ServletException {

        int count = 0;
        for (final String key : messageModifiers.keySet()) {
            // now search for the messages we just sent and update them appropriately by filling in their "read",
            // "deleted", and "added" properties, and stripping their "searchKey" property
            final MessageFilter filter = new MessageFilter();
            filter.addConstraint(new ValueConstraint<String>("jcr:title", key));
            final Map<String, Object> messageModifier = (Map<String, Object>) messageModifiers.get(key);
            final Map<String, Map<String, Boolean>> recipientModifiers = (Map<String, Map<String, Boolean>>) messageModifier
                    .get("recipientDetails");
            Iterable<Message> messages = null;
            try {
                messages = messageSearch.search(request.getResourceResolver(), filter, 0,
                        recipientModifiers.size() + 1);
            } catch (final RepositoryException e) {
                throw new ServletException("Sent messages could not be found", e);
            }
            if (!messages.iterator().hasNext()) {
                throw new ServletException("Sent messages could not be found");
            }
            for (final Message message : messages) {
                final Resource messageResource = message.adaptTo(Resource.class);
                ModifiableValueMap mvm = messageResource.adaptTo(ModifiableValueMap.class);
                mvm.put("added", messageModifier.get("added"));
                mvm.put("jcr:title", messageModifier.get("subject")); // restore the correct value for subject
                LOG.debug("Identified {} with path {}", messageModifier.get("subject"), messageResource.getPath());
                for (final String userPath : recipientModifiers.keySet()) {
                    if (messageResource.getPath().contains(userPath)) {
                        final Map<String, Boolean> modifiers = recipientModifiers.get(userPath);
                        message.setRead(modifiers.get("read"));
                        message.setDeleted(modifiers.get("deleted"));
                        break;
                    }
                }
                count++;
                if (count >= 50) {
                    // make sure we commit our update in batches no bigger than 50 to prevent sending POST's too large
                    // for an AS endpoint to handle
                    try {
                        request.getResourceResolver().commit(); //save changes
                    } catch (PersistenceException e) {
                        throw new ServletException("Messages were sent but not adjusted", e);
                    }
                    count = 0;
                }
            }
        }
        try {
            request.getResourceResolver().commit(); //save changes
        } catch (PersistenceException e) {
            throw new ServletException("Messages were sent but not adjusted", e);
        }
    }

    static final class MessagingServiceTracker extends ServiceTracker {

        private final ConcurrentMap<String, MessagingOperationsService> serviceCache = new ConcurrentHashMap<String, MessagingOperationsService>();

        /**
         * Default constructor.
         * @param context The {@link org.osgi.framework.BundleContext}.
         */
        MessagingServiceTracker(final BundleContext context) {
            super(context, MessagingOperationsService.class.getName(), null);
        }

        /**
         * Get the {@link com.adobe.cq.social.messaging.client.endpoints.MessagingOperationsService} instance for the
         * specified path.
         * @param path The path used to determine the
         *            {@link com.adobe.cq.social.messaging.client.endpoints.MessagingOperationsService} to return.
         * @return The {@link com.adobe.cq.social.messaging.client.endpoints.MessagingOperationsService}
         */
        @CheckForNull
        MessagingOperationsService getService(@Nonnull final String path) {
            return this.serviceCache.get(path);
        }

        @Override
        public Object addingService(final ServiceReference reference) {
            final Object service = super.addingService(reference);
            if (null != service) {
                bindService(reference, service);
            }
            return service;
        }

        @Override
        public void modifiedService(final ServiceReference reference, final Object service) {
            unbindService(reference);
            if (null != service) {
                bindService(reference, service);
            }
        }

        @Override
        public void removedService(final ServiceReference reference, final Object service) {
            unbindService(reference);
            super.removedService(reference, service);
        }

        private void bindService(@Nonnull final ServiceReference reference, final Object service) {
            serviceCache.putIfAbsent(String.valueOf(reference.getProperty(SERVICE_SELECTOR_PROPERTY)),
                    (MessagingOperationsService) service);
        }

        private void unbindService(@Nonnull final ServiceReference reference) {
            serviceCache.remove(String.valueOf(reference.getProperty(SERVICE_SELECTOR_PROPERTY)));
        }
    }
}