com.adobe.communities.ugc.migration.legacyExport.UGCExportHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.communities.ugc.migration.legacyExport.UGCExportHelper.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.legacyExport;

import static com.adobe.cq.social.tally.TallyConstants.RESPONSE_PROPERTY;
import static com.adobe.cq.social.tally.TallyConstants.TIMESTAMP_PROPERTY;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;

import org.apache.commons.codec.binary.Base64;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.io.JSONWriter;
import org.apache.sling.jcr.resource.JcrResourceConstants;

import com.adobe.cq.social.commons.Comment;
import com.adobe.cq.social.forum.api.Post;
import com.adobe.cq.social.journal.JournalEntry;
import com.adobe.cq.social.storage.buckets.NestedBucketStorageSystem;
import com.adobe.cq.social.tally.PollResponse;
import com.adobe.cq.social.tally.ResponseValue;
import com.adobe.cq.social.tally.TallyConstants;

public class UGCExportHelper {

    private final static int DATA_ENCODING_CHUNK_SIZE = 1440;

    public static void extractSubNode(JSONWriter object, final Resource node) throws JSONException {
        final ValueMap childVm = node.adaptTo(ValueMap.class);
        extractProperties(object, childVm);
        final Iterable<Resource> childNodes = node.getChildren();
        if (childNodes != null) {
            object.key(ContentTypeDefinitions.LABEL_SUBNODES);
            object.object();
            for (final Resource subNode : childNodes) {
                object.key(subNode.getName());
                JSONWriter subObject = object.object();
                extractSubNode(subObject, subNode);
                object.endObject();
            }
            object.endObject();
        }
    }

    public static void extractAttachment(final Writer ioWriter, final JSONWriter writer, final Resource node)
            throws JSONException, UnsupportedEncodingException {
        Resource contentNode = node.getChild("jcr:content");
        if (contentNode == null) {
            writer.key(ContentTypeDefinitions.LABEL_ERROR);
            writer.value("provided resource was not an attachment - no content node beneath " + node.getPath());
            return;
        }
        ValueMap content = contentNode.adaptTo(ValueMap.class);
        if (!content.containsKey("jcr:mimeType") || !content.containsKey("jcr:data")) {
            writer.key(ContentTypeDefinitions.LABEL_ERROR);
            writer.value(
                    "provided resource was not an attachment - content node contained no attachment data under "
                            + node.getPath());
            return;
        }
        writer.key("filename");
        writer.value(URLEncoder.encode(node.getName(), "UTF-8"));
        writer.key("jcr:mimeType");
        writer.value(content.get("jcr:mimeType"));

        try {
            ioWriter.write(",\"jcr:data\":\"");
            final InputStream data = (InputStream) content.get("jcr:data");
            byte[] byteData = new byte[DATA_ENCODING_CHUNK_SIZE];
            int read = 0;
            while (read != -1) {
                read = data.read(byteData);
                if (read > 0 && read < DATA_ENCODING_CHUNK_SIZE) {
                    // make a right-size container for the byte data actually read
                    byte[] byteArray = new byte[read];
                    System.arraycopy(byteData, 0, byteArray, 0, read);
                    byte[] encodedBytes = Base64.encodeBase64(byteArray);
                    ioWriter.write(new String(encodedBytes));
                } else if (read == DATA_ENCODING_CHUNK_SIZE) {
                    byte[] encodedBytes = Base64.encodeBase64(byteData);
                    ioWriter.write(new String(encodedBytes));
                }
            }
            ioWriter.write("\"");
        } catch (IOException e) {
            writer.key(ContentTypeDefinitions.LABEL_ERROR);
            writer.value("IOException while getting attachment at " + node.getPath() + ": " + e.getMessage());
        }
    }

    protected static Map<String, Map<Long, ResponseValue>> getTallyResponses(Resource tallyResource)
            throws JSONException {
        Map<String, Map<Long, ResponseValue>> returnValue = new HashMap<String, Map<Long, ResponseValue>>();
        final ResourceResolver resolver = tallyResource.getResourceResolver();
        final String tallyPath = tallyResource.getPath();
        if (!tallyPath.startsWith("/content/usergenerated")) {
            tallyResource = resolver.resolve("/content/usergenerated" + tallyPath);
        }
        final Resource responsesNode = tallyResource.getChild(TallyConstants.RESPONSES_PATH);
        if (responsesNode == null) {
            return null;
        }
        NestedBucketStorageSystem bucketSystem = getBucketSystem(responsesNode);
        if (null == bucketSystem) {
            return null;
        }

        final Iterator<Resource> buckets = bucketSystem.listBuckets();
        while (buckets.hasNext()) {
            final Resource bucketResource = buckets.next();
            final Node bucketNode = bucketResource.adaptTo(Node.class);
            try {
                final NodeIterator userNodesInBucket = bucketNode.getNodes();
                while (userNodesInBucket.hasNext()) {
                    final Node userNode = userNodesInBucket.nextNode();
                    final NestedBucketStorageSystem userBucketSystem = getBucketSystem(
                            resolver.getResource(userNode.getPath()));
                    final Iterator<Resource> userBuckets = userBucketSystem.listBuckets();
                    final Map<Long, ResponseValue> userReturnValue = new HashMap<Long, ResponseValue>();
                    while (userBuckets.hasNext()) {
                        final NodeIterator userResponses = userBuckets.next().adaptTo(Node.class).getNodes();
                        while (userResponses.hasNext()) {
                            final Node responseNode = userResponses.nextNode();
                            final Long responseTimestamp = responseNode.getProperty(TIMESTAMP_PROPERTY).getLong();
                            userReturnValue.put(responseTimestamp,
                                    new PollResponse(responseNode.getProperty(RESPONSE_PROPERTY).getString()));
                        }
                    }
                    returnValue.put(userNode.getName(), userReturnValue);
                }
            } catch (final RepositoryException e) {
                throw new JSONException(
                        "Error trying to read user responses from bucket in " + bucketResource.getPath(), e);
            }
        }
        return returnValue;
    }

    /**
     * Need to do this because the responses that tally returns don't actually link users with their response values.
     * In order to do an export under the legacy code, we have to explicitly handle the bucketing system.
     * @param resource - the resource being exported
     * @return NestedBucketStorageSystem
     */
    protected static NestedBucketStorageSystem getBucketSystem(final Resource resource) {
        if (null == resource) {
            return null;
        }
        NestedBucketStorageSystem bucketSystem;
        bucketSystem = resource.adaptTo(NestedBucketStorageSystem.class);
        if (bucketSystem != null) {
            bucketSystem.setBucketPostfix(NestedBucketStorageSystem.DEFAULT_BUCKET_POSTFIX);
        }
        return bucketSystem;
    }

    public static void extractTally(final JSONWriter responseArray, final Resource rootNode, final String tallyType)
            throws JSONException, UnsupportedEncodingException {

        final Map<String, Map<Long, ResponseValue>> responses = getTallyResponses(rootNode);
        if (null != responses) {
            for (final String userIdentifier : responses.keySet()) {
                for (final Map.Entry<Long, ResponseValue> entry : responses.get(userIdentifier).entrySet()) {
                    final JSONWriter voteObject = responseArray.object();
                    final String response = entry.getValue().getResponseValue();
                    voteObject.key("timestamp");
                    voteObject.value(entry.getKey());
                    voteObject.key("response");
                    voteObject.value(URLEncoder.encode(response, "UTF-8"));
                    voteObject.key("userIdentifier");
                    voteObject.value(URLEncoder.encode(userIdentifier, "UTF-8"));
                    if (tallyType != null) {
                        // for the purposes of this export, tallyType is fixed
                        voteObject.key("tallyType");
                        voteObject.value(tallyType);
                    }
                    voteObject.endObject();
                }
            }
        }
    }

    public static void extractTranslations(final JSONWriter writer, final Iterable<Resource> translations)
            throws JSONException, IOException {
        for (final Resource translation : translations) {
            final JSONArray timestampFields = new JSONArray();
            final ValueMap vm = translation.adaptTo(ValueMap.class);
            if (!vm.containsKey("jcr:description")) {
                continue; //if there's no translation, we're done here
            }
            String languageLabel = translation.getName();
            if (languageLabel.equals("nb")) {
                // SPECIAL CASE FOR LEGACY EXPORTER ONLY:
                // the label for norwegian changed between 6.0 and 6.1
                // (i.e. this section must be removed for 6.1 exporter)
                languageLabel = "no";
            }
            writer.key(languageLabel);

            JSONWriter translationObject = writer.object();
            translationObject.key("jcr:description");
            translationObject.value(URLEncoder.encode((String) vm.get("jcr:description"), "UTF-8"));
            if (vm.containsKey("jcr:createdBy")) {
                translationObject.key("jcr:createdBy");
                translationObject.value(URLEncoder.encode((String) vm.get("jcr:createdBy"), "UTF-8"));
            }
            if (vm.containsKey("jcr:title")) {
                translationObject.key("jcr:title");
                translationObject.value(URLEncoder.encode((String) vm.get("jcr:title"), "UTF-8"));
            }
            if (vm.containsKey("postEdited")) {
                translationObject.key("postEdited");
                translationObject.value(vm.get("postEdited"));
            }
            if (vm.containsKey("translationDate")) {
                translationObject.key("translationDate");
                translationObject.value(((Calendar) vm.get("translationDate")).getTimeInMillis());
                timestampFields.put("translationDate");
            }
            if (vm.containsKey("jcr:created")) {
                translationObject.key("jcr:created");
                translationObject.value(((Calendar) vm.get("jcr:created")).getTimeInMillis());
                timestampFields.put("jcr:created");
            }
            if (timestampFields.length() > 0) {
                translationObject.key(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS);
                translationObject.value(timestampFields);
            }
            translationObject.endObject();
        }
    }

    public static void extractComment(final JSONWriter writer, final Comment post, final ResourceResolver resolver,
            final Writer responseWriter) throws JSONException, IOException {

        final ValueMap vm = post.getProperties();
        final JSONArray timestampFields = new JSONArray();
        for (final Map.Entry<String, Object> prop : vm.entrySet()) {
            final Object value = prop.getValue();
            if (value instanceof String[]) {
                final JSONArray list = new JSONArray();
                for (String v : (String[]) value) {
                    list.put(v);
                }
                writer.key(prop.getKey());
                writer.value(list);
            } else if (prop.getKey().equals("sentiment")) {
                writer.key(prop.getKey());
                //1 = 1, 2 = 3, 3 = 5, 4 = 8, 5 = 10
                short shortValue = Short.parseShort(value.toString());
                switch (shortValue) {
                case 1:
                    writer.value(1);
                    break;
                case 2:
                    writer.value(3);
                    break;
                case 3:
                    writer.value(5);
                    break;
                case 4:
                    writer.value(8);
                    break;
                case 5:
                    writer.value(10);
                    break;
                default:
                    writer.value(value);
                }
            } else if (value instanceof GregorianCalendar) {
                timestampFields.put(prop.getKey());
                writer.key(prop.getKey());
                writer.value(((Calendar) value).getTimeInMillis());
            } else if (prop.getKey().equals("sling:resourceType")) {
                writer.key(prop.getKey());
                writer.value(Comment.RESOURCE_TYPE);
            } else {
                writer.key(prop.getKey());
                try {
                    writer.value(URLEncoder.encode(prop.getValue().toString(), "UTF-8"));
                } catch (final UnsupportedEncodingException e) {
                    throw new JSONException("Unsupported encoding (UTF-8) for resource at " + post.getPath(), e);
                }
            }
        }
        if (timestampFields.length() > 0) {
            writer.key(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS);
            writer.value(timestampFields);
        }
        final Resource thisResource = resolver.getResource(post.getPath());
        final Resource attachments = thisResource.getChild("attachments");
        if (attachments != null) {
            writer.key(ContentTypeDefinitions.LABEL_ATTACHMENTS);
            final JSONWriter attachmentsWriter = writer.array();
            for (final Resource attachment : attachments.getChildren()) {
                UGCExportHelper.extractAttachment(responseWriter, attachmentsWriter.object(), attachment);
                attachmentsWriter.endObject();
            }
            writer.endArray();
        }
        final Iterable<Resource> children = thisResource.getChildren();
        for (final Resource child : children) {
            if (child.isResourceType("social/tally/components/hbs/voting")
                    || child.isResourceType("social/tally/components/voting")) {
                writer.key(ContentTypeDefinitions.LABEL_TALLY);
                final JSONWriter voteObjects = writer.array();
                UGCExportHelper.extractTally(voteObjects, child, "Voting");
                writer.endArray();
            } else if (child.getName().equals("translation")) {
                extractTranslation(writer, child);
            }
        }
        final Iterator<Comment> posts = post.getComments();
        if (posts.hasNext()) {
            writer.key(ContentTypeDefinitions.LABEL_REPLIES);
            final JSONWriter replyWriter = writer.object();
            while (posts.hasNext()) {
                Comment childPost = posts.next();
                replyWriter.key(childPost.getId());
                extractComment(replyWriter.object(), childPost, resolver, responseWriter);
                replyWriter.endObject();
            }
            writer.endObject();
        }
    }

    public static void extractTopic(final JSONWriter writer, final Post post, final ResourceResolver resolver,
            final String resourceType, final String childResourceType, final Writer responseWriter)
            throws JSONException, IOException {

        final ValueMap vm = post.getProperties();
        final JSONArray timestampFields = new JSONArray();
        for (final Map.Entry<String, Object> prop : vm.entrySet()) {
            final Object value = prop.getValue();
            if (value instanceof String[]) {
                final JSONArray list = new JSONArray();
                for (String v : (String[]) value) {
                    list.put(v);
                }
                writer.key(prop.getKey());
                writer.value(list);
            } else if (value instanceof GregorianCalendar) {
                timestampFields.put(prop.getKey());
                writer.key(prop.getKey());
                writer.value(((Calendar) value).getTimeInMillis());
            } else if (prop.getKey().equals("sling:resourceType")) {
                writer.key(prop.getKey());
                writer.value(resourceType);
            } else if (prop.getKey().equals("sentiment")) {
                writer.key(prop.getKey());
                //1 = 1, 2 = 3, 3 = 5, 4 = 8, 5 = 10
                short shortValue = Short.parseShort(value.toString());
                switch (shortValue) {
                case 1:
                    writer.value(1);
                    break;
                case 2:
                    writer.value(3);
                    break;
                case 3:
                    writer.value(5);
                    break;
                case 4:
                    writer.value(8);
                    break;
                case 5:
                    writer.value(10);
                    break;
                default:
                    writer.value(value);
                }
            } else {
                writer.key(prop.getKey());
                try {
                    writer.value(URLEncoder.encode(prop.getValue().toString(), "UTF-8"));
                } catch (final UnsupportedEncodingException e) {
                    throw new JSONException("Unsupported encoding (UTF-8) for resource at " + post.getPath(), e);
                }
            }
        }
        if (timestampFields.length() > 0) {
            writer.key(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS);
            writer.value(timestampFields);
        }
        final Resource thisResource = resolver.getResource(post.getPath());
        final Resource attachments = thisResource.getChild("attachments");
        if (attachments != null) {
            writer.key(ContentTypeDefinitions.LABEL_ATTACHMENTS);
            final JSONWriter attachmentsWriter = writer.array();
            for (final Resource attachment : attachments.getChildren()) {
                UGCExportHelper.extractAttachment(responseWriter, attachmentsWriter.object(), attachment);
                attachmentsWriter.endObject();
            }
            writer.endArray();
        }
        final Iterable<Resource> children = thisResource.getChildren();
        for (final Resource child : children) {
            if (child.isResourceType("social/tally/components/hbs/voting")
                    || child.isResourceType("social/tally/components/voting")) {
                writer.key(ContentTypeDefinitions.LABEL_TALLY);
                final JSONWriter voteObjects = writer.array();
                UGCExportHelper.extractTally(voteObjects, child, "Voting");
                writer.endArray();
            } else if (child.getName().equals("translation")) {
                extractTranslation(writer, child);
            }
        }
        final Iterator<Post> posts = post.getPosts();
        if (posts.hasNext()) {
            writer.key(ContentTypeDefinitions.LABEL_REPLIES);
            final JSONWriter replyWriter = writer.object();
            while (posts.hasNext()) {
                Post childPost = posts.next();
                replyWriter.key(childPost.getId());
                extractTopic(replyWriter.object(), childPost, resolver, childResourceType, childResourceType,
                        responseWriter);
                replyWriter.endObject();
            }
            writer.endObject();
        }
    }

    public static void extractJournalEntry(final JSONWriter entryObject, final JournalEntry entry,
            final Writer rawWriter) throws JSONException, IOException {
        final Resource thisResource = entry.getTextComment().getResource();
        final ValueMap vm = thisResource.adaptTo(ValueMap.class);
        final JSONArray timestampFields = new JSONArray();
        // make sure we only migrate the fields we want
        final Map<String, Boolean> fieldsToMigrate = new HashMap<String, Boolean>();
        fieldsToMigrate.put("userIdentifier", true);
        fieldsToMigrate.put("authorizableId", true);
        fieldsToMigrate.put("published", true);
        fieldsToMigrate.put("jcr:description", true);
        fieldsToMigrate.put("jcr:title", true);
        fieldsToMigrate.put("negative", true);
        fieldsToMigrate.put("positive", true);
        fieldsToMigrate.put("sentiment", true);
        for (final Map.Entry<String, Object> prop : vm.entrySet()) {
            if (!fieldsToMigrate.containsKey(prop.getKey())) {
                continue;
            }
            final Object value = prop.getValue();
            if (prop.getKey().equals("published") && value instanceof GregorianCalendar) {
                timestampFields.put("added");
                entryObject.key("added");
                entryObject.value(((Calendar) value).getTimeInMillis());
            } else {
                entryObject.key(prop.getKey());
                try {
                    entryObject.value(URLEncoder.encode(prop.getValue().toString(), "UTF-8"));
                } catch (final UnsupportedEncodingException e) {
                    throw new JSONException(
                            "Unsupported encoding (UTF-8) for resource at " + thisResource.getPath(), e);
                }
            }
        }
        // resource type has changed, so ignore the current one and force the new one
        entryObject.key("sling:resourceType");
        entryObject.value("social/journal/components/hbs/entry_topic");
        if (timestampFields.length() > 0) {
            entryObject.key(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS);
            entryObject.value(timestampFields);
        }
        final Resource translationResource = thisResource.getChild("translation");
        if (null != translationResource) {
            extractTranslation(entryObject, translationResource);
        }
        if (entry.hasAttachments()) {
            entryObject.key(ContentTypeDefinitions.LABEL_ATTACHMENTS);
            JSONWriter attachmentsArray = entryObject.array();
            List<Resource> attachmentList = entry.getAttachments();
            for (final Resource attachment : attachmentList) {
                extractAttachment(rawWriter, attachmentsArray.object(), attachment);
                attachmentsArray.endObject();
            }
            entryObject.endArray();
        }
        if (entry.hasComments()) {
            final Iterator<Comment> posts = entry.getComments();
            entryObject.key(ContentTypeDefinitions.LABEL_REPLIES);
            final JSONWriter replyWriter = entryObject.object();
            while (posts.hasNext()) {
                final Comment childPost = posts.next();
                replyWriter.key(childPost.getId());
                extractComment(replyWriter.object(), childPost, entry.getResource().getResourceResolver(),
                        rawWriter);
                replyWriter.endObject();
            }
            entryObject.endObject();
        }
    }

    public static void extractProperties(final JSONWriter object, final Map<String, Object> properties)
            throws JSONException {
        extractProperties(object, properties, null, null);
    }

    public static void extractTranslation(final JSONWriter writer, final Resource translationResource)
            throws JSONException, IOException {

        final Iterable<Resource> translations = translationResource.getChildren();
        final ValueMap props = translationResource.adaptTo(ValueMap.class);
        String languageLabel = (String) props.get("language");
        if (null == languageLabel) {
            languageLabel = (String) props.get("mtlanguage");
            if (null == languageLabel) {
                return;
            }
        }
        writer.key(ContentTypeDefinitions.LABEL_TRANSLATION);
        writer.object();
        writer.key("mtlanguage");
        if (languageLabel.equals("nb")) {
            // SPECIAL CASE FOR LEGACY EXPORTER ONLY:
            // the label for norwegian changed between 6.0 and 6.1
            // (i.e. this section must be removed for 6.1 exporter)
            languageLabel = "no";
        }

        writer.value(languageLabel);
        writer.key("jcr:created");
        writer.value(props.get("jcr:created", Long.class));
        writer.key("jcr:createdBy");
        writer.value(props.get("jcr:createdBy"));
        if (translations.iterator().hasNext()) {
            writer.key(ContentTypeDefinitions.LABEL_TRANSLATIONS);
            final JSONWriter translationObjects = writer.object();
            UGCExportHelper.extractTranslations(translationObjects, translations);
            writer.endObject();
        }
        writer.endObject();
    }

    public static void extractProperties(final JSONWriter object, final Map<String, Object> properties,
            final Map<String, String> renamedProperties, final String resourceType) throws JSONException {
        final JSONArray timestampFields = new JSONArray();
        boolean setResourceType = false;
        for (Map.Entry<String, Object> prop : properties.entrySet()) {
            Object value = prop.getValue();
            String key;
            if (null != renamedProperties && renamedProperties.containsKey(prop.getKey())) {
                key = renamedProperties.get(prop.getKey());
                if (null == key) {
                    continue; // we're excluding this property from the export
                }
            } else {
                key = prop.getKey();
            }
            if (null != resourceType && key.equals(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY)) {
                value = resourceType;
                setResourceType = true;
            }
            if (value instanceof String[]) {
                final JSONArray list = new JSONArray();
                for (String v : (String[]) value) {
                    try {
                        list.put(URLEncoder.encode(v, "UTF-8"));
                    } catch (final UnsupportedEncodingException e) {
                        throw new JSONException("String value cannot be encoded as UTF-8 for JSON transmission", e);
                    }
                }
                object.key(key);
                object.value(list);
            } else if (value instanceof GregorianCalendar) {
                timestampFields.put(key);
                object.key(key);
                object.value(((Calendar) value).getTimeInMillis());
            } else if (value instanceof InputStream) {
                object.key(ContentTypeDefinitions.LABEL_ENCODED_DATA_FIELDNAME);
                object.value(key);
                object.key(ContentTypeDefinitions.LABEL_ENCODED_DATA);
                try {
                    final InputStream data = (InputStream) value;
                    byte[] byteData = new byte[DATA_ENCODING_CHUNK_SIZE];
                    int read = 0;
                    final StringBuilder stringBuilder = new StringBuilder();
                    while (read != -1) {
                        read = data.read(byteData);
                        if (read > 0 && read < DATA_ENCODING_CHUNK_SIZE) {
                            // make a right-size container for the byte data actually read
                            byte[] byteArray = new byte[read];
                            System.arraycopy(byteData, 0, byteArray, 0, read);
                            byte[] encodedBytes = Base64.encodeBase64(byteArray);
                            stringBuilder.append(new String(encodedBytes));
                        } else if (read == DATA_ENCODING_CHUNK_SIZE) {
                            byte[] encodedBytes = Base64.encodeBase64(byteData);
                            stringBuilder.append(new String(encodedBytes));
                        }
                    }
                    object.value(stringBuilder.toString());
                } catch (IOException e) {
                    object.value(""); // if we error out on the first read attempt, we need a placeholder value still
                    object.key(ContentTypeDefinitions.LABEL_ERROR);
                    object.value("IOException while getting attachment: " + e.getMessage());
                }
            } else {
                object.key(key);
                try {
                    object.value(URLEncoder.encode(value.toString(), "UTF-8"));
                } catch (final UnsupportedEncodingException e) {
                    throw new JSONException("String value cannot be encoded as UTF-8 for JSON transmission", e);
                }
            }
        }
        if (null != resourceType && !setResourceType) { // make sure this gets included if it's been specified
            object.key(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY);
            object.value(resourceType);
        }
        if (timestampFields.length() > 0) {
            object.key(ContentTypeDefinitions.LABEL_TIMESTAMP_FIELDS);
            object.value(timestampFields);
        }
    }
}