/*
* � Copyright IBM Corp. 2012
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.ibm.domino.commons.json;
import static com.ibm.domino.commons.json.JsonConstants.BCC_PROP;
import static com.ibm.domino.commons.json.JsonConstants.CC_PROP;
import static com.ibm.domino.commons.json.JsonConstants.CONTENT_PROP;
import static com.ibm.domino.commons.json.JsonConstants.DATE_PROP;
import static com.ibm.domino.commons.json.JsonConstants.DISTINGUISHED_NAME_PROP;
import static com.ibm.domino.commons.json.JsonConstants.EMAIL_PROP;
import static com.ibm.domino.commons.json.JsonConstants.FROM_PROP;
import static com.ibm.domino.commons.json.JsonConstants.HREF_PROP;
import static com.ibm.domino.commons.json.JsonConstants.IN_REPLY_TO_PROP;
import static com.ibm.domino.commons.json.JsonConstants.MESSAGE_ID_PROP;
import static com.ibm.domino.commons.json.JsonConstants.RECEIPT_TO_PROP;
import static com.ibm.domino.commons.json.JsonConstants.SUBJECT_PROP;
import static com.ibm.domino.commons.json.JsonConstants.THREADID_PROP;
import static com.ibm.domino.commons.json.JsonConstants.TO_PROP;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.Vector;
import lotus.domino.DateTime;
import lotus.domino.Document;
import lotus.domino.MIMEEntity;
import lotus.domino.Name;
import lotus.domino.NotesException;
import lotus.domino.Session;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonObject;
import com.ibm.domino.commons.internal.Logger;
import com.ibm.domino.commons.json.JsonMimeEntityAdapter.ParserContext;
import com.ibm.domino.commons.mime.MimeEntityHelper;
import com.ibm.domino.commons.model.Person;
import com.ibm.domino.commons.util.BackendUtil;
public class JsonMessageAdapter implements JsonObject {
public static final String FORM_ITEM = "Form"; // $NON-NLS-1$
public static final String MEMO_FORM = "Memo"; // $NON-NLS-1$
public static final String REPLY_FORM = "Reply"; // $NON-NLS-1$
private static final String SENDTO_ITEM = "SendTo"; // $NON-NLS-1$
private static final String ENTERSENDTO_ITEM = "EnterSendTo"; // $NON-NLS-1$
private static final String COPYTO_ITEM = "CopyTo"; // $NON-NLS-1$
private static final String ENTERCOPYTO_ITEM = "EnterCopyTo"; // $NON-NLS-1$
private static final String BLINDCOPYTO_ITEM = "BlindCopyTo"; // $NON-NLS-1$
private static final String ENTERBLINDCOPYTO_ITEM = "EnterBlindCopyTo"; // $NON-NLS-1$
private static final String FROM_ITEM = "From"; // $NON-NLS-1$
private static final String MESSAGE_ID_ITEM = "$MessageID"; // $NON-NLS-1$
private static final String IN_REPLY_TO_ITEM = "In_Reply_To"; // $NON-NLS-1$
private static final String RETURN_RECEIPT_ITEM = "ReturnReceipt"; //$NON-NLS-1$
private static final String DISPOSITION_NOTIFICATION_TO_ITEM = "Disposition_Notification_To"; //$NON-NLS-1$
private static final String POSTEDDATE_ITEM = "PostedDate"; // $NON-NLS-1$
private static final String SUBJECT_ITEM = "Subject"; // $NON-NLS-1$
private static final String BODY_ITEM = "Body"; // $NON-NLS-1$
private static final String INET_PREFIX = "INet"; // $NON-NLS-1$
private static final String ENTER_PREFIX = "Enter"; // $NON-NLS-1$
private static final String THREADID_ITEM = "$TUA"; // $NON-NLS-1$
private static SimpleDateFormat ISO8601 = getUtcFormatter();
private Document _document = null;
private String _url = null;
private String[] _propertyNames = null;
private ParserContext _context = null;
/**
* Constructor used when generating JSON output.
*
* @param document
* @param url
*/
public JsonMessageAdapter(Document document, String url) {
_document = document;
_url = url;
}
/**
* Constructor used when parsing JSON input.
*
* @param document
* @param context
*/
public JsonMessageAdapter(Document document, ParserContext context) {
_document = document;
_context = context;
// SPR #BBRL9QYA6A: Remove old items from the document
removeOldItems();
}
public Iterator<String> getJsonProperties() {
return new Iterator<String>() {
private int _index = 0;
public boolean hasNext() {
String properties[] = getProperties();
return _index < properties.length ;
}
public String next() {
String properties[] = getProperties();
return properties[_index++];
}
public void remove() {
// The JSON IO classes shouldn't call remove
}
@SuppressWarnings("unchecked")//$NON-NLS-1$
private String[] getProperties() {
if ( _propertyNames != null ) {
return _propertyNames;
}
List<String> properties = new ArrayList<String>();
try {
String from = _document.getItemValueString(FROM_ITEM);
if ( StringUtil.isNotEmpty(from) ) {
properties.add(FROM_PROP);
}
Vector<Object> items = _document.getItemValue(SENDTO_ITEM);
if ( items != null ) {
Iterator<Object> iterator = items.iterator();
if ( iterator.hasNext() ) {
properties.add(TO_PROP);
}
// SPR #BBRL9R99N7: replace item "SendTo" with "EnterSendTo" when there is no "SendTo" in Drafts view
// Similar with "CopyTo" and "BlindCopyTo"
else if ( !_document.hasItem(POSTEDDATE_ITEM ) ) {
items = _document.getItemValue(ENTERSENDTO_ITEM);
if ( items != null ) {
iterator = items.iterator();
if ( iterator.hasNext() ) {
properties.add(TO_PROP);
}
}
}
}
items = _document.getItemValue(COPYTO_ITEM);
if ( items != null ) {
Iterator<Object> iterator = items.iterator();
if ( iterator.hasNext() ) {
properties.add(CC_PROP);
}
else if ( !_document.hasItem(POSTEDDATE_ITEM ) ) {
items = _document.getItemValue(ENTERCOPYTO_ITEM);
if ( items != null ) {
iterator = items.iterator();
if ( iterator.hasNext() ) {
properties.add(CC_PROP);
}
}
}
}
items = _document.getItemValue(BLINDCOPYTO_ITEM);
if ( items != null ) {
Iterator<Object> iterator = items.iterator();
if ( iterator.hasNext() ) {
properties.add(BCC_PROP);
}
else if ( !_document.hasItem(POSTEDDATE_ITEM ) ) {
items = _document.getItemValue(ENTERBLINDCOPYTO_ITEM);
if ( items != null ) {
iterator = items.iterator();
if ( iterator.hasNext() ) {
properties.add(BCC_PROP);
}
}
}
}
String subject = _document.getItemValueString(SUBJECT_ITEM);
if ( StringUtil.isNotEmpty(subject) ) {
properties.add(SUBJECT_PROP);
}
String messageId = _document.getItemValueString(MESSAGE_ID_ITEM);
if ( StringUtil.isNotEmpty(messageId) ) {
properties.add(MESSAGE_ID_PROP);
}
String inReplyTo = _document.getItemValueString(IN_REPLY_TO_ITEM);
if ( StringUtil.isNotEmpty(inReplyTo) ) {
properties.add(IN_REPLY_TO_PROP);
}
String returnReceipt = _document.getItemValueString(RETURN_RECEIPT_ITEM);
if ( "1".equals(returnReceipt) ) {
properties.add(RECEIPT_TO_PROP);
}
properties.add(DATE_PROP);
properties.add(HREF_PROP);
properties.add(CONTENT_PROP);
items = _document.getItemValue(THREADID_ITEM);
if (items != null) {
Iterator<Object> iterator = items.iterator();
if (iterator.hasNext()) {
properties.add(THREADID_PROP);
}
}
}
catch(NotesException e) {
// Ignore
Logger.get().warnp(this, "getProperties",//$NON-NLS-1$
e, "Unhandled exception getting the list of JSON property names");// $NLW-JsonMessageAdapter_UnhandledExceptionInGetProperties-1$
}
// Convert to array
String[] array = new String[properties.size()];
Iterator<String> iterator = properties.iterator();
for ( int i = 0; iterator.hasNext(); i++ ) {
array[i] = iterator.next();
}
// Cache the array for next time
_propertyNames = array;
return _propertyNames;
}
};
}
@SuppressWarnings("rawtypes")//$NON-NLS-1$
public Object getJsonProperty(String property) {
Object value = null;
// This method is called when generating a JSON message
// from an existing Notes document
try {
if ( FROM_PROP.equals(property) ) {
String from = _document.getItemValueString(FROM_ITEM);
if ( StringUtil.isNotEmpty(from) ) {
String email = _document.getItemValueString(INET_PREFIX + FROM_ITEM);
if ( StringUtil.isEmpty(email) ) {
email = null;
}
Session session = _document.getParentDatabase().getParent();
Name name = null;
try {
// This try block is here to force a recycle of the name object
// before it goes out of scope
name = session.createName(from);
Person person = new Person(name.getCommon(), name.getAbbreviated(), email);
value = new JsonPersonAdapter(person);
}
finally {
BackendUtil.safeRecycle(name);
}
}
}
else if ( SUBJECT_PROP.equals(property) ) {
String subject = _document.getItemValueString(SUBJECT_ITEM);
if ( subject != null ) {
value = subject;
}
}
else if ( MESSAGE_ID_PROP.equals(property) ) {
String messageId = _document.getItemValueString(MESSAGE_ID_ITEM);
if ( messageId != null ) {
value = messageId;
}
}
else if ( IN_REPLY_TO_PROP.equals(property) ) {
String inReplyTo = _document.getItemValueString(IN_REPLY_TO_ITEM);
if ( inReplyTo != null ) {
value = inReplyTo;
}
}
else if ( RECEIPT_TO_PROP.equals(property) ) {
Session session = _document.getParentDatabase().getParent();
String receiptTo = _document.getItemValueString(DISPOSITION_NOTIFICATION_TO_ITEM);
String inetReceiptTo = null;
if ( StringUtil.isEmpty(receiptTo) ) {
receiptTo = _document.getItemValueString(FROM_ITEM);
inetReceiptTo = _document.getItemValueString(INET_PREFIX + FROM_ITEM);
}
if ( StringUtil.isNotEmpty(receiptTo)) {
String displayName = null;
String distinguishedName = null;
String emailAddress = null;
Name name = null;
try {
// This try block is here to force a recycle of the name object
// before it goes out of scope
name = session.createName(receiptTo);
if ( name.isHierarchical() ) {
displayName = name.getCommon();
distinguishedName = name.getAbbreviated();
if ( StringUtil.isNotEmpty(inetReceiptTo) ) {
emailAddress = inetReceiptTo;
}
}
else {
displayName = trimCommonName(name.getCommon());
emailAddress = name.getAddr821();
}
}
finally {
BackendUtil.safeRecycle(name);
}
Person person = new Person(displayName, distinguishedName, emailAddress);
value = new JsonPersonAdapter(person);
}
}
else if ( DATE_PROP.equals(property) ) {
DateTime dt = null;
if ( _document.hasItem(POSTEDDATE_ITEM)) {
Vector values = _document.getItemValueDateTimeArray(POSTEDDATE_ITEM);
if ( values != null && values.size() > 0 ) {
if ( values.get(0) instanceof DateTime ) {
dt = (DateTime)values.get(0);
}
}
}
if ( dt == null ) {
dt = _document.getLastModified();
}
Date date = dt.toJavaDate();
value = ISO8601.format(date);
}
else if ( HREF_PROP.equals(property) ) {
value = _url;
}
else if ( TO_PROP.equals(property) ) {
List<JsonPersonAdapter> listValue = getAddressList(SENDTO_ITEM);
if ( listValue.size() > 0 ) {
value = listValue;
}
else if ( !_document.hasItem(POSTEDDATE_ITEM) ) {
value = getAddressList(ENTERSENDTO_ITEM);
}
}
else if ( CC_PROP.equals(property) ) {
List<JsonPersonAdapter> listValue = getAddressList(COPYTO_ITEM);
if ( listValue.size() > 0 ) {
value = listValue;
}
else if ( !_document.hasItem(POSTEDDATE_ITEM) ) {
value = getAddressList(ENTERCOPYTO_ITEM);
}
}
else if ( BCC_PROP.equals(property) ) {
List<JsonPersonAdapter> listValue = getAddressList(BLINDCOPYTO_ITEM);
if ( listValue.size() > 0 ) {
value = listValue;
}
else if ( !_document.hasItem(POSTEDDATE_ITEM) ) {
value = getAddressList(ENTERBLINDCOPYTO_ITEM);
}
}
else if ( CONTENT_PROP.equals(property) ) {
List<JsonMimeEntityAdapter> adapters = new ArrayList<JsonMimeEntityAdapter>();
MimeEntityHelper helper = new MimeEntityHelper(_document, BODY_ITEM);
MIMEEntity entity = helper.getFirstMimeEntity();
if ( entity != null ) {
JsonMimeEntityAdapter.addEntityAdapter(adapters, entity);
}
value = adapters;
} else if ( THREADID_PROP.equals(property) ) {
Vector values = _document.getItemValue(THREADID_ITEM);
if ( values != null && values.size() > 0 ) {
value = values.get(0).toString();
}
}
}
catch (NotesException e) {
Logger.get().warnp(this, "getJsonProperty",//$NON-NLS-1$
e, "Unhandled exception getting a JSON property value");// $NLW-JsonMessageAdapter_UnhandledExceptionInGetJsonProperty-1$
// default to null
}
return value;
}
public void putJsonProperty(String property, Object value) {
// This method is called when converting JSON to a message
// PLEASE READ if you are adding a new JSON property here.
// When you write a document item here, you should also remove
// it in removeOldItems(). That's important when updating
// an existing document from the JSON input.
try {
if ( TO_PROP.equals(property) ) {
putAddressList(SENDTO_ITEM, value);
}
else if ( CC_PROP.equals(property) ) {
putAddressList(COPYTO_ITEM, value);
}
else if ( BCC_PROP.equals(property) ) {
putAddressList(BLINDCOPYTO_ITEM, value);
}
else if ( SUBJECT_PROP.equals(property) ) {
_document.replaceItemValue(SUBJECT_ITEM, value);
}
else if ( IN_REPLY_TO_PROP.equals(property) ) {
_document.replaceItemValue(IN_REPLY_TO_ITEM, value);
// SPR #DDEY9L6MX5: Change the Form item to indicate it's a reply
_document.replaceItemValue(FORM_ITEM, REPLY_FORM);
}
else if ( RECEIPT_TO_PROP.equals(property) ) {
if ( value instanceof JsonPersonAdapter ) {
JsonPersonAdapter adapter = (JsonPersonAdapter)value;
String email = (String)adapter.getJsonProperty(EMAIL_PROP);
String distinguishedName = (String)adapter.getJsonProperty(DISTINGUISHED_NAME_PROP);
String itemValue = null;
if ( StringUtil.isNotEmpty(distinguishedName) ) {
itemValue = distinguishedName;
}
else if ( StringUtil.isNotEmpty(email) ) {
itemValue = email;
}
if ( itemValue != null ) {
_document.replaceItemValue(DISPOSITION_NOTIFICATION_TO_ITEM, itemValue);
_document.replaceItemValue(RETURN_RECEIPT_ITEM, "1");
}
}
}
else if ( CONTENT_PROP.equals(property) ) {
// Flush the last MIME entity to the document
JsonMimeEntityAdapter entityAdapter = _context.getCurrentEntityAdapter();
if ( entityAdapter != null ) {
entityAdapter.flushJsonProperties(true);
}
}
}
catch (NotesException e) {
// Ignore !?!
}
catch (JsonException e) {
// Ignore !?!
}
}
private static SimpleDateFormat getUtcFormatter() {
TimeZone tz = TimeZone.getTimeZone("UTC"); // $NON-NLS-1$
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //$NON-NLS-1$
formatter.setTimeZone(tz);
return formatter;
}
/**
* Creates a list of JsonPersonAdapters from a list of Notes addresses
*
* <p>This method may get data from items other than <code>itemName</code>. For example,
* if the item name is SendTo, this method reads the INetSendTo item for corresponding
* internet addresses.
*
* @param itemName
* @return
* @throws NotesException
*/
private List<JsonPersonAdapter> getAddressList(String itemName) throws NotesException {
List<JsonPersonAdapter> list = new ArrayList<JsonPersonAdapter>();
Vector<Object> items = _document.getItemValue(itemName);
Vector<Object> inetItems = _document.getItemValue(INET_PREFIX + itemName);
Session session = _document.getParentDatabase().getParent();
Iterator<Object> inetIterator = null;
if ( inetItems != null ) {
inetIterator = inetItems.iterator();
}
if ( items != null ) {
Iterator<Object> iterator = items.iterator();
while ( iterator.hasNext() ) {
Object value = iterator.next();
Object inetValue = null;
if ( inetIterator != null && inetIterator.hasNext() ) {
inetValue = inetIterator.next();
}
if ( value instanceof String ) {
String displayName = null;
String distinguishedName = null;
String emailAddress = null;
Name name = null;
try {
// This try block is here to force a recycle of the name object
// before it goes out of scope
name = session.createName((String)value);
if ( name.isHierarchical() ) {
displayName = name.getCommon();
distinguishedName = name.getAbbreviated();
if ( inetValue instanceof String ) {
emailAddress = (String)inetValue;
}
else {
emailAddress = name.getAddr821();
}
}
else {
displayName = trimCommonName(name.getCommon());
emailAddress = name.getAddr821();
}
}
finally {
BackendUtil.safeRecycle(name);
}
Person person = new Person(displayName, distinguishedName, emailAddress);
list.add(new JsonPersonAdapter(person));
}
}
}
return list;
}
private void putAddressList(String itemName, Object list) throws NotesException {
Vector<Object> values = new Vector<Object>();
if ( list instanceof List ) {
Iterator<Object> iterator = ((List)list).iterator();
while (iterator.hasNext()) {
Object value = iterator.next();
if ( value instanceof JsonPersonAdapter ) {
JsonPersonAdapter adapter = (JsonPersonAdapter)value;
String email = (String)adapter.getJsonProperty(EMAIL_PROP);
String distinguishedName = (String)adapter.getJsonProperty(DISTINGUISHED_NAME_PROP);
if ( StringUtil.isNotEmpty(distinguishedName) ) {
values.add(distinguishedName);
}
else if ( StringUtil.isNotEmpty(email) ) {
values.add(email);
}
}
}
_document.replaceItemValue(itemName, values);
}
}
/**
* Removes leading and trailing quotes and/or whitespace.
*
* @param name
* @return
*/
private String trimCommonName(String name) {
name = name.replaceAll("(^\")|(\"$)", ""); // $NON-NLS-1$
name = name.trim();
return name;
}
/**
* Removes old items from the document.
*/
private void removeOldItems() {
try {
_document.removeItem(SENDTO_ITEM);
_document.removeItem(ENTER_PREFIX + SENDTO_ITEM);
_document.removeItem(INET_PREFIX + SENDTO_ITEM);
_document.removeItem(COPYTO_ITEM);
_document.removeItem(ENTER_PREFIX + COPYTO_ITEM);
_document.removeItem(INET_PREFIX + COPYTO_ITEM);
_document.removeItem(BLINDCOPYTO_ITEM);
_document.removeItem(ENTER_PREFIX + BLINDCOPYTO_ITEM);
_document.removeItem(INET_PREFIX + BLINDCOPYTO_ITEM);
_document.removeItem(SUBJECT_ITEM);
_document.removeItem(IN_REPLY_TO_ITEM);
_document.removeItem(DISPOSITION_NOTIFICATION_TO_ITEM);
_document.removeItem(RETURN_RECEIPT_ITEM);
_document.removeItem(BODY_ITEM);
}
catch (NotesException e) {
// Ignore
}
}
}