com.ntsync.shared.ContactGroup.java Source code

Java tutorial

Introduction

Here is the source code for com.ntsync.shared.ContactGroup.java

Source

package com.ntsync.shared;

/*
 * Copyright (C) 2014 Markus Grieder
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>. 
 */

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.modes.AEADBlockCipher;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

/**
 * Group for a RawContact
 */
public final class ContactGroup {

    private static final int DEFAULT_BYTEARRAY_SIZE = 100;

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

    private final Long rawId;
    private final String sourceId;
    private final String title;
    private final String notes;
    private final boolean deleted;
    private final long version;

    private Date lastModified;

    public ContactGroup(Long rawId, String sourceId, String title, String notes, boolean deleted, Date lastModified,
            long version) {
        super();
        this.rawId = rawId;
        this.sourceId = sourceId;
        this.title = title;
        this.notes = notes;
        this.deleted = deleted;
        this.lastModified = lastModified;
        this.version = version;
    }

    public Long getRawId() {
        return rawId;
    }

    public String getSourceId() {
        return sourceId;
    }

    public String getTitle() {
        return title;
    }

    public String getNotes() {
        return notes;
    }

    public boolean isDeleted() {
        return deleted;
    }

    public Date getLastModified() {
        return lastModified;
    }

    public long getVersion() {
        return version;
    }

    /**
     * Serialize this ContactGroup for transporting to a server
     * 
     * @param secret
     * @param pwdSaltBase64
     * @return null if serializing failed.
     */
    public byte[] toDTO(Key secret, String pwdSaltBase64) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream(DEFAULT_BYTEARRAY_SIZE);

            AEADBlockCipher ecipher = CryptoHelper.getCipher();

            byte[] iv = new byte[CryptoHelper.IV_LEN];
            SecureRandom random = new SecureRandom();

            StringBuilder hashValue = new StringBuilder();
            hashValue.append(pwdSaltBase64);
            hashValue.append(title);

            out.write(GroupConstants.ROWID);

            byte[] rowId = String.valueOf(rawId).getBytes(SyncDataHelper.DEFAULT_CHARSET_NAME);

            SyncDataHelper.writeInt(out, rowId.length);
            out.write(rowId);

            JsonFactory json = new JsonFactory();
            StringWriter writer = new StringWriter();
            JsonGenerator g = json.createGenerator(writer);
            g.writeStartObject();

            writeField(g, GroupConstants.TITLE, title);
            writeField(g, GroupConstants.NOTES, notes);

            g.writeEndObject();
            g.close();

            String textData = writer.toString();

            CryptoHelper.writeValue(secret, out, ecipher, iv, random, GroupConstants.TEXTDATA, textData);

            if (lastModified != null) {
                writeRawValue(out, GroupConstants.MODIFIED,
                        String.valueOf(lastModified.getTime()).getBytes(SyncDataHelper.DEFAULT_CHARSET_NAME));
            }

            if (deleted) {
                writeRawValue(out, GroupConstants.DELETED, "1".getBytes(SyncDataHelper.DEFAULT_CHARSET_NAME));
            }
            if (sourceId != null) {
                writeRawValue(out, GroupConstants.SERVERROW_ID,
                        sourceId.getBytes(SyncDataHelper.DEFAULT_CHARSET_NAME));
            }

            MessageDigest md = MessageDigest.getInstance("SHA-256");

            md.update(hashValue.toString().getBytes(SyncDataHelper.DEFAULT_CHARSET_NAME));
            byte[] hash = md.digest();
            writeRawValue(out, GroupConstants.HASH, hash);

            return out.toByteArray();
        } catch (final Exception ex) {
            LOG.error("Error converting ContactGroup to ByteStream: " + ex.toString(), ex);
        }
        return null;
    }

    /**
     * Creates and returns an instance of the RawContact from encrypted data
     * 
     * */
    public static ContactGroup valueOf(String rowId, Map<Byte, ByteBuffer> values, Key privateKey)
            throws InvalidKeyException {
        try {
            String sourceId = null;
            Long rawId = null;

            if (values.containsKey(GroupConstants.SERVERROW_ID)) {
                sourceId = readRawString(values.get(GroupConstants.SERVERROW_ID));
            }

            if (sourceId == null || !sourceId.equals(rowId)) {
                // If ServerContactId is different, then rowId is the clientId
                rawId = Long.parseLong(rowId);
            }

            if (sourceId == null && rawId < 0) {
                throw new IllegalArgumentException("Missing RowId in data");
            }

            AEADBlockCipher cipher = CryptoHelper.getCipher();

            final boolean deleted = values.containsKey(GroupConstants.DELETED);

            final String textData = CryptoHelper.decodeStringValue(GroupConstants.TEXTDATA, values, cipher,
                    privateKey);

            if (textData == null && !deleted) {
                LOG.error("No textdata found for row with Id:" + rowId);
                return null;
            }

            String title = null;
            String notes = null;

            if (!isEmpty(textData)) {
                JsonFactory fac = new JsonFactory();
                JsonParser jp = fac.createParser(textData);
                jp.nextToken();
                while (jp.nextToken() != JsonToken.END_OBJECT) {
                    String fieldname = jp.getCurrentName();
                    // move to value, or START_OBJECT/START_ARRAY
                    jp.nextToken();
                    if (GroupConstants.TITLE.equals(fieldname)) {
                        title = jp.getValueAsString();
                    } else if (GroupConstants.NOTES.equals(fieldname)) {
                        notes = jp.getValueAsString();
                    } else {
                        LOG.error("Unrecognized field for row with Id:" + rowId + " Fieldname:" + fieldname);
                    }
                }
                jp.close();
            }

            String modStr = readRawString(values.get(GroupConstants.MODIFIED));
            Date lastModified = null;
            if (!isEmpty(modStr)) {
                lastModified = new Date(Long.parseLong(modStr));
            }

            return new ContactGroup(rawId, sourceId, title, notes, deleted, lastModified, -1);
        } catch (InvalidCipherTextException ex) {
            throw new InvalidKeyException("Invalid key detected.", ex);
        } catch (final Exception ex) {
            LOG.info("Error parsing contactgroup data. Reason:" + ex.toString(), ex);
        }
        return null;
    }

    private static String readRawString(ByteBuffer src) throws UnsupportedEncodingException {
        int len = src != null ? src.remaining() : -1;
        if (len >= 0) {
            byte[] output = new byte[len];
            src.get(output, 0, len);
            // Android has UTF-8 as default
            return new String(output, SyncDataHelper.DEFAULT_CHARSET_NAME);
        }
        return null;
    }

    private static boolean isEmpty(String text) {
        return text == null || text.length() == 0;
    }

    private void writeRawValue(ByteArrayOutputStream out, byte key, byte[] value) throws IOException {
        if (value != null && value.length > 0) {
            // 1byte key, //4 len //value
            out.write(key);

            SyncDataHelper.writeInt(out, value.length);
            out.write(value);
        }
    }

    private static void writeField(JsonGenerator g, String fielName, String field) throws IOException {
        if (field != null && field.length() > 0) {
            g.writeStringField(fielName, field);
        }
    }

    public static ContactGroup createDeletedGroup(long rawId, String sourceId) {
        return new ContactGroup(rawId, sourceId, null, null, true, null, -1);
    }

    public void setLastModified(Date lastModified) {
        this.lastModified = lastModified;
    }
}