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

Java tutorial

Introduction

Here is the source code for com.adobe.communities.ugc.migration.importer.UGCImportHelper.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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;

import javax.activation.DataSource;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.ServletException;

import com.adobe.cq.social.tally.TallyConstants;
import org.apache.commons.codec.binary.Base64;
import org.apache.felix.scr.annotations.Reference;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.resource.ModifyingResourceProvider;
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.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.communities.ugc.migration.ContentTypeDefinitions;
import com.adobe.cq.social.calendar.client.endpoints.CalendarOperations;
import com.adobe.cq.social.calendar.client.endpoints.CalendarRequestConstants;
import com.adobe.cq.social.commons.Comment;
import com.adobe.cq.social.commons.FileDataSource;
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.scf.OperationException;
import com.adobe.cq.social.srp.SocialResourceProvider;
import com.adobe.cq.social.srp.config.SocialResourceConfiguration;
import com.adobe.cq.social.tally.Tally;
import com.adobe.cq.social.tally.Voting;
import com.adobe.cq.social.tally.client.api.RatingSocialComponent;
import com.adobe.cq.social.tally.client.api.VotingSocialComponent;
import com.adobe.cq.social.tally.client.endpoints.TallyOperationsService;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.adobe.cq.social.ugcbase.core.SocialResourceUtils;
import com.adobe.granite.security.user.UserProperties;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

public class UGCImportHelper {
    private static final Logger LOG = LoggerFactory.getLogger(UGCImportHelper.class);

    @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;

    private SocialResourceProvider resProvider;

    /**
     * These values ought to come from com.adobe.cq.social.calendar.CalendarConstants, but that class isn't in the
     * uberjar, so I'll define the constants here instead.
     */
    final static String PN_START = "calendar_event_start_dt";
    final static String PN_END = "calendar_event_end_dt";

    public UGCImportHelper() {
    }

    public void setTallyService(final TallyOperationsService tallyOperationsService) {
        if (this.tallyOperationsService == null)
            this.tallyOperationsService = tallyOperationsService;
    }

    public void setForumOperations(final ForumOperations forumOperations) {
        if (this.forumOperations == null)
            this.forumOperations = forumOperations;
    }

    public void setCommentOperations(final CommentOperations commentOperations) {
        if (this.commentOperations == null)
            this.commentOperations = commentOperations;
    }

    public void setQnaForumOperations(final QnaForumOperations qnaForumOperations) {
        if (this.qnaForumOperations == null)
            this.qnaForumOperations = qnaForumOperations;
    }

    public void setCalendarOperations(final CalendarOperations calendarOperations) {
        if (this.calendarOperations == null)
            this.calendarOperations = calendarOperations;
    }

    public void setJournalOperations(final JournalOperations journalOperations) {
        if (this.journalOperations == null)
            this.journalOperations = journalOperations;
    }

    public void setSocialUtils(final SocialUtils socialUtils) {
        if (this.socialUtils == null)
            this.socialUtils = socialUtils;
    }

    public Resource extractResource(final JsonParser parser, final SocialResourceProvider provider,
            final ResourceResolver resolver, final String path) throws IOException {
        final Map<String, Object> properties = new HashMap<String, Object>();

        while (parser.nextToken() != JsonToken.END_OBJECT) {
            String name = parser.getCurrentName();
            parser.nextToken();
            JsonToken token = parser.getCurrentToken();

            if (name.equals(ContentTypeDefinitions.LABEL_SUBNODES)) {
                if (token.equals(JsonToken.START_OBJECT)) {
                    parser.nextToken();
                    final String childPath = path + "/" + parser.getCurrentName();
                    parser.nextToken(); // should equal JsonToken.START_OBJECT
                    final Resource childResource = extractResource(parser, provider, resolver, childPath); // should
                    // we do anything with this?
                }
            }
        }

        return provider.create(resolver, path, properties);
    }

    public static Map<String, Object> extractSubmap(final JsonParser jsonParser) throws IOException {
        jsonParser.nextToken(); // skip the START_OBJECT token
        final Map<String, Object> subMap = new HashMap<String, Object>();
        while (!jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
            final String label = jsonParser.getCurrentName(); // get the current label
            final JsonToken token = jsonParser.nextToken(); // get the current value
            if (!token.isScalarValue()) {
                if (token.equals(JsonToken.START_OBJECT)) {
                    // if the next token starts a new object, recurse into it
                    subMap.put(label, extractSubmap(jsonParser));
                } else if (token.equals(JsonToken.START_ARRAY)) {
                    final List<String> subArray = new ArrayList<String>();
                    jsonParser.nextToken(); // skip the START_ARRAY token
                    while (!jsonParser.getCurrentToken().equals(JsonToken.END_ARRAY)) {
                        subArray.add(jsonParser.getValueAsString());
                        jsonParser.nextToken();
                    }
                    subMap.put(label, subArray);
                    jsonParser.nextToken(); // skip the END_ARRAY token
                }
            } else {
                // either a string, boolean, or long value
                if (token.isNumeric()) {
                    subMap.put(label, jsonParser.getValueAsLong());
                } else {
                    final String value = jsonParser.getValueAsString();
                    if (value.equals("true") || value.equals("false")) {
                        subMap.put(label, jsonParser.getValueAsBoolean());
                    } else {
                        subMap.put(label, value);
                    }
                }
            }
            jsonParser.nextToken(); // next token will either be an "END_OBJECT" or a new label
        }
        jsonParser.nextToken(); // skip the END_OBJECT token
        return subMap;
    }

    public static void extractTally(final Resource post, final JsonParser jsonParser,
            final ModifyingResourceProvider srp, final TallyOperationsService tallyOperationsService)
            throws IOException {
        jsonParser.nextToken(); // should be start object, but would be end array if no objects were present
        while (!jsonParser.getCurrentToken().equals(JsonToken.END_ARRAY)) {
            Long timestamp = null;
            String userIdentifier = null;
            String response = null;
            String tallyType = null;
            jsonParser.nextToken(); // should make current token by "FIELD_NAME" but could be END_OBJECT if this were
            // an empty object
            while (!jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
                final String label = jsonParser.getCurrentName();
                jsonParser.nextToken(); // should be FIELD_VALUE
                if (label.equals(TallyConstants.TIMESTAMP_PROPERTY)) {
                    timestamp = jsonParser.getValueAsLong();
                } else {
                    final String responseValue = jsonParser.getValueAsString();
                    if (label.equals("response")) {
                        response = URLDecoder.decode(responseValue, "UTF-8");
                    } else if (label.equals("userIdentifier")) {
                        userIdentifier = URLDecoder.decode(responseValue, "UTF-8");
                    } else if (label.equals("tallyType")) {
                        tallyType = responseValue;
                    }
                }
                jsonParser.nextToken(); // should make current token be "FIELD_NAME" unless we're at the end of our
                // loop and it's now "END_OBJECT" instead
            }
            if (timestamp != null && userIdentifier != null && response != null && tallyType != null) {
                createTally(srp, post, tallyType, userIdentifier, timestamp, response, tallyOperationsService);
            }
            jsonParser.nextToken(); // may advance to "START_OBJECT" if we're not finished yet, but might be
            // "END_ARRAY" now
        }
    }

    private static void createTally(final ModifyingResourceProvider srp, final Resource post,
            final String tallyType, final String userIdentifier, final Long timestamp, final String response,
            final TallyOperationsService tallyOperationsService) throws PersistenceException {

        final ResourceResolver resolver = post.getResourceResolver();
        final Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("jcr:primaryType", "social:asiResource");
        final Calendar calendar = new GregorianCalendar();
        calendar.setTimeInMillis(timestamp);
        properties.put("jcr:created", calendar.getTime());
        Resource tallyResource = null;
        Tally tally;
        if (tallyType.equals(TallyOperationsService.VOTING)) {
            if (post.getResourceType().equals(VotingSocialComponent.VOTING_RESOURCE_TYPE)) {
                tallyResource = post;
            } else {
                Iterable<Resource> postChildren = post.getChildren();
                for (final Resource postChild : postChildren) {
                    if (postChild.getResourceType().equals(VotingSocialComponent.VOTING_RESOURCE_TYPE)) {
                        tallyResource = postChild;
                        break;
                    }
                }
                if (tallyResource == null) {
                    properties.put("sling:resourceType", VotingSocialComponent.VOTING_RESOURCE_TYPE);
                    properties.put("social:parentid", post.getPath());
                    tallyResource = resolver.create(post, "voting", properties);
                    try {
                        resolver.commit();
                    } catch (Exception e) {
                        // ignoring exception to let the rest of the file get imported
                        LOG.error("Could not create vote {} on post {}", tallyResource, post);
                    }
                    properties.remove("sling:resourceType");
                }
            }
            tally = tallyResource.adaptTo(Voting.class);
            tally.setTallyResourceType(VotingSocialComponent.VOTING_RESOURCE_TYPE);
        } else if (tallyType.equals(TallyOperationsService.POLL)) {
            // don't throw an exception, since we know what it is, but log a warning since we no longer support polls
            LOG.warn("Unsupported tally type 'poll' could not be imported");
            return;
        } else if (tallyType.equals(TallyOperationsService.RATING)) {
            if (post.getResourceType().equals(RatingSocialComponent.RATING_RESOURCE_TYPE)) {
                tallyResource = post;
            } else {
                Iterable<Resource> postChildren = post.getChildren();
                for (final Resource postChild : postChildren) {
                    if (postChild.getResourceType().equals(RatingSocialComponent.RATING_RESOURCE_TYPE)) {
                        tallyResource = postChild;
                        break;
                    }
                }
                if (tallyResource == null) {
                    properties.put("sling:resourceType", RatingSocialComponent.RATING_RESOURCE_TYPE);
                    tallyResource = srp.create(resolver, post.getPath() + "/rating_" + randomHexString(),
                            properties);
                    srp.commit(resolver);
                    properties.remove("sling:resourceType");
                }
            }
            tally = tallyResource.adaptTo(Voting.class);
            tally.setTallyResourceType(RatingSocialComponent.RATING_RESOURCE_TYPE);
        } else {
            throw new RuntimeException("unrecognized tally type");
        }
        // Needed params:
        try {
            properties.put(TallyConstants.TIMESTAMP_PROPERTY, calendar);
            tallyOperationsService.setTallyResponse(tally.getTallyTarget(), userIdentifier,
                    resolver.adaptTo(Session.class), response, tallyType, properties);
        } catch (final OperationException e) {
            throw new RuntimeException("Unable to set the tally response value: " + e.getMessage(), e);
        } catch (final IllegalArgumentException e) {
            // We can ignore this. It means that the value set for the response in the migrated data is no longer
            // valid. This happens for "#neutral#" which used to be a valid response, but was taken out in later
            // versions.
        }
    }

    public void importQnaContent(final JsonParser jsonParser, final Resource resource,
            final ResourceResolver resolver) throws ServletException, IOException {
        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            jsonParser.nextToken(); // advance to first key in the object - should be the id value of the old post
            while (jsonParser.getCurrentToken().equals(JsonToken.FIELD_NAME)) {
                extractTopic(jsonParser, resource, resolver, qnaForumOperations);
                jsonParser.nextToken(); // get the next token - presumably a field name for the next post
            }
            jsonParser.nextToken(); // skip end token
        } else {
            throw new IOException("Improperly formed JSON - expected an OBJECT_START token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    public void importForumContent(final JsonParser jsonParser, final Resource resource,
            final ResourceResolver resolver) throws ServletException, IOException {
        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            jsonParser.nextToken(); // advance to first key in the object - should be the id value of the old post
            while (jsonParser.getCurrentToken().equals(JsonToken.FIELD_NAME)) {
                extractTopic(jsonParser, resource, resolver, forumOperations);
                jsonParser.nextToken(); // get the next token - presumably a field name for the next post
            }
            jsonParser.nextToken(); // skip end token
        } else {
            throw new IOException("Improperly formed JSON - expected an OBJECT_START token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    public void importCommentsContent(final JsonParser jsonParser, final Resource resource,
            final ResourceResolver resolver) throws ServletException, IOException {
        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            jsonParser.nextToken(); // advance to first key in the object - should be the id value of the old post
            while (jsonParser.getCurrentToken().equals(JsonToken.FIELD_NAME)) {
                extractTopic(jsonParser, resource, resolver, commentOperations);
                jsonParser.nextToken(); // get the next token - presumably a field name for the next post
            }
            jsonParser.nextToken(); // skip end token
        } else {
            throw new IOException("Improperly formed JSON - expected an OBJECT_START token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    public void importJournalContent(final JsonParser jsonParser, final Resource resource,
            final ResourceResolver resolver) throws IOException, ServletException {

        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            jsonParser.nextToken(); // advance to first key in the object - should be the id value of the old post
            while (jsonParser.getCurrentToken().equals(JsonToken.FIELD_NAME)) {
                extractTopic(jsonParser, resource, resolver, journalOperations);
                jsonParser.nextToken(); // get the next token - presumably a field name for the next post
            }
            jsonParser.nextToken(); // skip end token
        } else {
            throw new IOException("Improperly formed JSON - expected an OBJECT_START token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    public void importCalendarContent(final JsonParser jsonParser, final Resource resource) throws IOException {
        if (jsonParser.getCurrentToken().equals(JsonToken.START_ARRAY)) {
            jsonParser.nextToken(); // skip START_ARRAY here
        } else {
            throw new IOException("Improperly formed JSON - expected an START_ARRAY token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            while (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
                extractEvent(jsonParser, resource);
                jsonParser.nextToken(); // get the next token - either a start object token or an end array token
            }
        } else {
            throw new IOException("Improperly formed JSON - expected an OBJECT_START token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    public void importTallyContent(final JsonParser jsonParser, final Resource resource) throws IOException {

        SocialResourceConfiguration config = socialUtils.getStorageConfig(resource);
        final String rootPath = config.getAsiPath();
        resProvider = SocialResourceUtils.getSocialResource(resource.getResourceResolver().getResource(rootPath))
                .getResourceProvider();
        if (jsonParser.getCurrentToken().equals(JsonToken.START_ARRAY)) {
            extractTally(resource, jsonParser, resProvider, tallyOperationsService);
            jsonParser.nextToken(); // get the next token - either a start object token or an end array token
        } else {
            throw new IOException("Improperly formed JSON - expected an START_ARRAY token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    protected void extractTopic(final JsonParser jsonParser, final Resource resource,
            final ResourceResolver resolver, final CommentOperations operations)
            throws IOException, ServletException {
        if (jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
            return; // replies could just be an empty object (i.e. "ugc:replies":{} ) in which case, do nothing
        }
        final Map<String, Object> properties = new HashMap<String, Object>();
        properties.put("social:key", jsonParser.getCurrentName());
        Resource post = null;
        jsonParser.nextToken();
        if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
            jsonParser.nextToken();
            String author = null;
            List<DataSource> attachments = new ArrayList<DataSource>();
            while (!jsonParser.getCurrentToken().equals(JsonToken.END_OBJECT)) {
                final String label = jsonParser.getCurrentName();
                JsonToken token = jsonParser.nextToken();
                if (jsonParser.getCurrentToken().isScalarValue()) {

                    // either a string, boolean, or long value
                    if (token.isNumeric()) {
                        properties.put(label, jsonParser.getValueAsLong());
                    } else {
                        final String value = jsonParser.getValueAsString();
                        if (value.equals("true") || value.equals("false")) {
                            properties.put(label, jsonParser.getValueAsBoolean());
                        } else {
                            final String decodedValue = URLDecoder.decode(value, "UTF-8");
                            if (label.equals("language")) {
                                properties.put("mtlanguage", decodedValue);
                            } else {
                                properties.put(label, decodedValue);
                                if (label.equals("userIdentifier")) {
                                    author = decodedValue;
                                } else if (label.equals("jcr:description")) {
                                    properties.put("message", decodedValue);
                                }
                            }
                        }
                    }
                } else if (label.equals(ContentTypeDefinitions.LABEL_ATTACHMENTS)) {
                    attachments = getAttachments(jsonParser);
                } else if (label.equals(ContentTypeDefinitions.LABEL_REPLIES)
                        || label.equals(ContentTypeDefinitions.LABEL_TALLY)
                        || label.equals(ContentTypeDefinitions.LABEL_TRANSLATION)
                        || label.equals(ContentTypeDefinitions.LABEL_SUBNODES)) {
                    // replies and sub-nodes ALWAYS come after all other properties and attachments have been listed,
                    // so we can create the post now if we haven't already, and then dive in
                    if (post == null) {
                        try {
                            post = createPost(resource, author, properties, attachments,
                                    resolver.adaptTo(Session.class), operations);
                            resProvider = SocialResourceUtils.getSocialResource(post).getResourceProvider();
                        } catch (Exception e) {
                            throw new ServletException(e.getMessage(), e);
                        }
                    }
                    if (label.equals(ContentTypeDefinitions.LABEL_REPLIES)) {
                        if (token.equals(JsonToken.START_OBJECT)) {
                            jsonParser.nextToken();
                            while (!token.equals(JsonToken.END_OBJECT)) {
                                extractTopic(jsonParser, post, resolver, operations);
                                token = jsonParser.nextToken();
                            }
                        } else {
                            throw new IOException("Expected an object for the subnodes");
                        }
                    } else if (label.equals(ContentTypeDefinitions.LABEL_SUBNODES)) {
                        if (token.equals(JsonToken.START_OBJECT)) {
                            token = jsonParser.nextToken();
                            try {
                                while (!token.equals(JsonToken.END_OBJECT)) {
                                    final String subnodeType = jsonParser.getCurrentName();
                                    token = jsonParser.nextToken();
                                    if (token.equals(JsonToken.START_OBJECT)) {
                                        jsonParser.skipChildren();
                                        token = jsonParser.nextToken();
                                    }
                                }
                            } catch (final IOException e) {
                                throw new IOException("unable to skip child of sub-nodes", e);
                            }
                        } else {
                            final String field = jsonParser.getValueAsString();
                            throw new IOException("Expected an object for the subnodes. Instead: " + field);
                        }
                    } else if (label.equals(ContentTypeDefinitions.LABEL_TALLY)) {
                        UGCImportHelper.extractTally(post, jsonParser, resProvider, tallyOperationsService);
                    } else if (label.equals(ContentTypeDefinitions.LABEL_TRANSLATION)) {
                        importTranslation(jsonParser, post);
                        resProvider.commit(post.getResourceResolver());
                    }

                } else if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
                    properties.put(label, UGCImportHelper.extractSubmap(jsonParser));
                } else if (jsonParser.getCurrentToken().equals(JsonToken.START_ARRAY)) {
                    jsonParser.nextToken(); // skip the START_ARRAY token
                    if (label.equals(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS)) {
                        while (!jsonParser.getCurrentToken().equals(JsonToken.END_ARRAY)) {
                            final String timestampLabel = jsonParser.getValueAsString();
                            if (properties.containsKey(timestampLabel)
                                    && properties.get(timestampLabel) instanceof Long) {
                                final Calendar calendar = new GregorianCalendar();
                                calendar.setTimeInMillis((Long) properties.get(timestampLabel));
                                properties.put(timestampLabel, calendar.getTime());
                            }
                            jsonParser.nextToken();
                        }
                    } else {
                        final List<String> subArray = new ArrayList<String>();
                        while (!jsonParser.getCurrentToken().equals(JsonToken.END_ARRAY)) {
                            subArray.add(jsonParser.getValueAsString());
                            jsonParser.nextToken();
                        }
                        String[] strings = new String[subArray.size()];
                        for (int i = 0; i < subArray.size(); i++) {
                            strings[i] = subArray.get(i);
                        }
                        properties.put(label, strings);
                    }
                }
                jsonParser.nextToken();
            }
            if (post == null) {
                try {
                    post = createPost(resource, author, properties, attachments, resolver.adaptTo(Session.class),
                            operations);
                    if (null == resProvider) {
                        resProvider = SocialResourceUtils.getSocialResource(post).getResourceProvider();
                    }
                    // resProvider.commit(resolver);
                } catch (Exception e) {
                    throw new ServletException(e.getMessage(), e);
                }
            }
        } else {
            throw new IOException("Improperly formed JSON - expected an OBJECT_START token, but got "
                    + jsonParser.getCurrentToken().toString());
        }
    }

    protected static Resource createPost(final Resource resource, final String author,
            final Map<String, Object> properties, final List<DataSource> attachments, final Session session,
            final CommentOperations operations) throws OperationException {
        if (populateMessage(properties)) {
            return operations.create(resource, author, properties, attachments, session);
        } else {
            return null;
        }
    }

    protected static boolean populateMessage(Map<String, Object> properties) {

        String message = null;
        if (properties.containsKey(Comment.PROP_MESSAGE)) {
            message = properties.get(Comment.PROP_MESSAGE).toString();
        }
        if (message == null || message.equals("")) {
            if (properties.containsKey("jcr:title")) {
                message = properties.get("jcr:title").toString();
                if (message == null || message.equals("")) {
                    // If title and message are both blank, we skip this entry and
                    // log the fact that we skipped it
                    LOG.warn("We failed to create a post because it had an empty message and title");
                    return false;
                } else {
                    properties.put(Comment.PROP_MESSAGE, message);
                    properties.put("message", message);
                }
            } else {
                // If title and message are both blank, we skip this entry and
                // log the fact that we skipped it
                LOG.warn("We failed to create a post because it had an empty message and title");
                return false;
            }
        }
        return true;
    }

    protected void extractEvent(final JsonParser jsonParser, final Resource resource) throws IOException {

        String author = null;
        final JSONObject requestParams = new JSONObject();
        try {
            jsonParser.nextToken();
            JsonToken token = jsonParser.getCurrentToken();
            while (token.equals(JsonToken.FIELD_NAME)) {
                String field = jsonParser.getCurrentName();
                jsonParser.nextToken();

                if (field.equals(PN_START) || field.equals(PN_END)) {
                    final Long value = jsonParser.getValueAsLong();
                    final Calendar calendar = new GregorianCalendar();
                    calendar.setTimeInMillis(value);
                    final Date date = calendar.getTime();
                    final TimeZone tz = TimeZone.getTimeZone("UTC");
                    // this is the ISO-8601 format expected by the CalendarOperations object
                    final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm+00:00");
                    df.setTimeZone(tz);
                    if (field.equals(PN_START)) {
                        requestParams.put(CalendarRequestConstants.START_DATE, df.format(date));
                    } else {
                        requestParams.put(CalendarRequestConstants.END_DATE, df.format(date));
                    }
                } else if (field.equals(CalendarRequestConstants.TAGS)) {
                    List<String> tags = new ArrayList<String>();
                    if (jsonParser.getCurrentToken().equals(JsonToken.START_ARRAY)) {
                        token = jsonParser.nextToken();
                        while (!token.equals(JsonToken.END_ARRAY)) {
                            tags.add(URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8"));
                            token = jsonParser.nextToken();
                        }
                        requestParams.put(CalendarRequestConstants.TAGS, tags);
                    } else {
                        LOG.warn("Tags field came in without an array of tags in it - not processed");
                        // do nothing, just log the error
                    }
                } else if (field.equals("jcr:createdBy")) {
                    author = URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8");
                } else if (field.equals(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS)) {
                    jsonParser.skipChildren(); // we do nothing with these because they're going to be put into json
                } else {
                    try {
                        final String value = URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8");
                        requestParams.put(field, value);
                    } catch (NullPointerException e) {
                        throw e;
                    }
                }
                token = jsonParser.nextToken();
            }
        } catch (final JSONException e) {
            throw new IOException("Unable to build a JSON object with the inputs provided", e);
        }
        try {
            Map<String, Object> eventParams = new HashMap<String, Object>();
            eventParams.put("event", requestParams.toString());
            calendarOperations.create(resource, author, eventParams, null,
                    resource.getResourceResolver().adaptTo(Session.class));
        } catch (final OperationException e) {
            //probably caused by creating a folder that already exists. We ignore it, but still log the event.
            LOG.info("There was an operation exception while creating an event");
        }
    }

    protected static List<DataSource> getAttachments(final JsonParser jsonParser) throws IOException {
        // an attachment has only 3 fields - jcr:data, filename, jcr:mimeType
        List<DataSource> attachments = new ArrayList<DataSource>();
        getAttachments(jsonParser, attachments);
        return attachments;
    }

    protected static void getAttachments(final JsonParser jsonParser, final List attachments) throws IOException {

        JsonToken token = jsonParser.nextToken(); // skip START_ARRAY token
        String filename;
        String mimeType;
        InputStream inputStream;
        while (token.equals(JsonToken.START_OBJECT)) {
            filename = null;
            mimeType = null;
            inputStream = null;
            byte[] databytes = null;
            token = jsonParser.nextToken();
            while (!token.equals(JsonToken.END_OBJECT)) {
                final String label = jsonParser.getCurrentName();
                jsonParser.nextToken();
                if (label.equals("filename")) {
                    filename = URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8");
                } else if (label.equals("jcr:mimeType")) {
                    mimeType = jsonParser.getValueAsString();
                } else if (label.equals("jcr:data")) {
                    databytes = Base64.decodeBase64(jsonParser.getValueAsString());
                    inputStream = new ByteArrayInputStream(databytes);
                }
                token = jsonParser.nextToken();
            }
            if (filename != null && mimeType != null && inputStream != null) {
                attachments.add(
                        new UGCImportHelper.AttachmentStruct(filename, inputStream, mimeType, databytes.length));
            } else {
                // log an error
                LOG.error(
                        "We expected to import an attachment, but information was missing and nothing was imported");
            }
            token = jsonParser.nextToken();
        }
    }

    protected static void importTranslation(final JsonParser jsonParser, final Resource post) throws IOException {
        JsonToken token = jsonParser.getCurrentToken();
        final Map<String, Object> properties = new HashMap<String, Object>();
        if (token != JsonToken.START_OBJECT) {
            throw new IOException("expected a start object token, got " + token.asString());
        }
        properties.put("jcr:primaryType", "social:asiResource");
        Resource translationFolder = null;
        token = jsonParser.nextToken();
        while (token == JsonToken.FIELD_NAME) {
            token = jsonParser.nextToken(); //advance to the field value
            if (jsonParser.getCurrentName().equals((ContentTypeDefinitions.LABEL_TRANSLATIONS))) {
                if (null == translationFolder) {
                    // begin by creating the translation folder resource
                    translationFolder = post.getResourceResolver().create(post, "translation", properties);
                }
                //now check to see if any translations exist
                if (token == JsonToken.START_OBJECT) {
                    token = jsonParser.nextToken();
                    if (token == JsonToken.FIELD_NAME) {
                        while (token == JsonToken.FIELD_NAME) { // each new field represents another translation
                            final Map<String, Object> translationProperties = new HashMap<String, Object>();
                            translationProperties.put("jcr:primaryType", "social:asiResource");
                            String languageLabel = jsonParser.getCurrentName();
                            token = jsonParser.nextToken();
                            if (token != JsonToken.START_OBJECT) {
                                throw new IOException("expected a start object token for translation item, got "
                                        + token.asString());
                            }
                            token = jsonParser.nextToken();
                            while (token != JsonToken.END_OBJECT) {
                                jsonParser.nextToken(); //get next field value
                                if (jsonParser.getCurrentName()
                                        .equals(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS)) {
                                    jsonParser.nextToken(); // advance to first field name
                                    while (!jsonParser.getCurrentToken().equals(JsonToken.END_ARRAY)) {
                                        final String timestampLabel = jsonParser.getValueAsString();
                                        if (translationProperties.containsKey(timestampLabel)) {
                                            final Calendar calendar = new GregorianCalendar();
                                            calendar.setTimeInMillis(Long
                                                    .parseLong((String) translationProperties.get(timestampLabel)));
                                            translationProperties.put(timestampLabel, calendar.getTime());
                                        }
                                        jsonParser.nextToken();
                                    }
                                } else if (jsonParser.getCurrentName()
                                        .equals(ContentTypeDefinitions.LABEL_SUBNODES)) {
                                    jsonParser.skipChildren();
                                } else {
                                    translationProperties.put(jsonParser.getCurrentName(),
                                            URLDecoder.decode(jsonParser.getValueAsString(), "UTF-8"));
                                }
                                token = jsonParser.nextToken(); //get next field label
                            }
                            // add the language-specific translation under the translation folder resource
                            Resource translation = post.getResourceResolver().create(post.getChild("translation"),
                                    languageLabel, translationProperties);
                            if (null == translation) {
                                throw new IOException("translation not actually imported");
                            }
                        }
                        jsonParser.nextToken(); //skip END_OBJECT token for translation
                    } else if (token == JsonToken.END_OBJECT) {
                        // no actual translation to import, so we're done here
                        jsonParser.nextToken();
                    }
                } else {
                    throw new IOException(
                            "expected translations to be contained in an object, saw instead: " + token.asString());
                }
            } else if (jsonParser.getCurrentName().equals("mtlanguage")
                    || jsonParser.getCurrentName().equals("jcr:createdBy")) {
                properties.put(jsonParser.getCurrentName(), jsonParser.getValueAsString());
            } else if (jsonParser.getCurrentName().equals("jcr:created")) {
                final Calendar calendar = new GregorianCalendar();
                calendar.setTimeInMillis(jsonParser.getLongValue());
                properties.put("jcr:created", calendar.getTime());
            }
            token = jsonParser.nextToken();
        }
        if (null == translationFolder && properties.containsKey("mtlanguage")) {
            // it's possible that no translations existed, so we need to make sure the translation resource (which
            // includes the original post's detected language) is created anyway
            post.getResourceResolver().create(post, "translation", properties);
        }
    }

    public static void checkUserPrivileges(final ResourceResolver resolver, final ResourceResolverFactory rrf)
            throws ServletException {

        // determine whether the current session belongs to the group administrators
        final UserManager um = resolver.adaptTo(UserManager.class);
        try {
            final Authorizable adminGroup = um.getAuthorizable("administrators");
            final Authorizable user = resolver.adaptTo(Authorizable.class);
            final Group administrators = (Group) adminGroup;
            if (!administrators.isMember(user)) {
                throw new ServletException("Insufficient access");
            }
        } catch (final RepositoryException e) {
            throw new ServletException("Cannot access repository", e);
        }
    }

    private static String randomHexString() {
        Random rand = new Random();
        int hexInt = rand.nextInt(0X10000);
        return Integer.toHexString(hexInt).toLowerCase();
    }

    /**
     * This class must implement DataSource in order to be used to create an attachment, and also FileDataSource in
     * order to be used by the filterAttachments method in AbstractCommentOperationService
     */
    public static class AttachmentStruct implements FileDataSource {
        private String filename;
        private String mimeType;
        private InputStream data;
        private long size;

        public AttachmentStruct(final String filename, final InputStream data, final String mimeType,
                final long size) {
            this.filename = filename;
            this.data = data;
            this.mimeType = mimeType;
            this.size = size;
        }

        public String getName() {
            return filename;
        }

        public String getContentType() {
            return mimeType;
        }

        public OutputStream getOutputStream() throws IOException {
            throw new IOException("OutputStream is not supported");
        }

        public InputStream getInputStream() {
            return data;
        }

        /**
         * Returns the MIME type of the content.
         * @return content MIME type.
         */
        public String getType() {
            return mimeType;
        }

        /**
         * Returns the MIME type extension from file name.
         * @return content MIME type extension from file Name.
         */
        public String getTypeFromFileName() {
            return filename.substring(filename.lastIndexOf('.'));
        }

        /**
         * Returns the size of the file in bytes.
         * @return size of file in bytes.
         */
        public long getSize() {
            return size;
        }
    }
}