/*******************************************************************************
* * Copyright 2012 Impetus Infotech.
* *
* * 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.impetus.client.mongodb;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.PersistenceException;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Metamodel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.impetus.client.mongodb.utils.MongoDBUtils;
import com.impetus.kundera.gis.geometry.Point;
import com.impetus.kundera.metadata.model.MetamodelImpl;
import com.impetus.kundera.metadata.model.attributes.AbstractAttribute;
import com.impetus.kundera.metadata.model.attributes.AttributeType;
import com.impetus.kundera.persistence.EntityReaderException;
import com.impetus.kundera.property.PropertyAccessException;
import com.impetus.kundera.property.PropertyAccessorHelper;
import com.impetus.kundera.property.accessor.EnumAccessor;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
/**
* Provides functionality for mapping between MongoDB documents and POJOs.
* Contains utility methods for converting one form into another.
*
* @author amresh.singh
*/
public class DocumentObjectMapper
{
/** The log. */
private static Logger log = LoggerFactory.getLogger(DocumentObjectMapper.class);
/**
* Creates a MongoDB document object wrt a given Java object. columns in the
* document correspond Columns provided as List.
*
* @param obj
* the obj
* @param columns
* the columns
* @return the document from object
* @throws PropertyAccessException
* the property access exception
*/
static Map<String, DBObject> getDocumentFromObject(Metamodel metaModel, Object obj, Set<Attribute> columns,
String tableName) throws PropertyAccessException
{
Map<String, DBObject> embeddedObjects = new HashMap<String, DBObject>();
BasicDBObject dBObj = new BasicDBObject();
for (Attribute column : columns)
{
String collectionName = ((AbstractAttribute) column).getTableName() != null ? ((AbstractAttribute) column)
.getTableName() : tableName;
DBObject dbObject = embeddedObjects.get(collectionName);
if (dbObject == null)
{
dbObject = new BasicDBObject();
embeddedObjects.put(collectionName, dbObject);
}
if (((MetamodelImpl) metaModel).isEmbeddable(((AbstractAttribute) column).getBindableJavaType()))
{
DefaultMongoDBDataHandler handler = new DefaultMongoDBDataHandler();
handler.onEmbeddable(column, obj, metaModel, dBObj, collectionName);
}
else
{
extractFieldValue(obj, dbObject, column);
}
}
return embeddedObjects;
}
/**
* Creates a MongoDB document list from a given java collection. columns in
* the document correspond Columns provided as List.
*
* @param coll
* the coll
* @param columns
* the columns
* @return the document list from collection
* @throws PropertyAccessException
* the property access exception
*/
static BasicDBObject[] getDocumentListFromCollection(Metamodel metaModel, Collection coll, Set<Attribute> columns,
String tableName) throws PropertyAccessException
{
BasicDBObject[] dBObjects = new BasicDBObject[coll.size()];
int count = 0;
for (Object o : coll)
{
dBObjects[count] = (BasicDBObject) getDocumentFromObject(metaModel, o, columns, tableName).values().toArray()[0];
count++;
}
return dBObjects;
}
/**
* Creates an instance of <code>clazz</code> and populates fields fetched
* from MongoDB document object. Field names are determined from
* <code>columns</code>
*
* @param documentObj
* the document obj
* @param clazz
* the clazz
* @param columns
* the columns
* @return the object from document
* @throws IllegalAccessException
* @throws InstantiationException
*/
static Object getObjectFromDocument(Metamodel metamodel, BasicDBObject documentObj, Set<Attribute> columns,
Object obj) throws InstantiationException, IllegalAccessException
{
// try
// {
// Object obj = clazz.newInstance();
for (Attribute column : columns)
{
if (((MetamodelImpl) metamodel).isEmbeddable(((AbstractAttribute) column).getBindableJavaType()))
{
DefaultMongoDBDataHandler handler = new DefaultMongoDBDataHandler();
handler.onViaEmbeddable(column, obj, metamodel, documentObj);
}
else
{
setFieldValue(documentObj, obj, column);
}
}
return obj;
// }
// catch (InstantiationException e)
// {
// throw new PersistenceException(e);
// }
// catch (IllegalAccessException e)
// {
// throw new PersistenceException(e);
// }
}
/**
* Setter for column value, by default converted from string value, in case
* of map it is automatically converted into map using BasicDBObject.
*
* @param document
* mongo document
* @param entityObject
* searched entity.
* @param column
* column field.
*/
static void setFieldValue(DBObject document, Object entityObject, Attribute column)
{
Object value = null;
if (document != null)
{
value = document.get(((AbstractAttribute) column).getJPAColumnName());
}
if (value != null)
{
Class javaType = column.getJavaType();
try
{
switch (AttributeType.getType(javaType))
{
case MAP:
PropertyAccessorHelper.set(entityObject, (Field) column.getJavaMember(),
((BasicDBObject) value).toMap());
break;
case SET:
List collectionValues = Arrays.asList(((BasicDBList) value).toArray());
PropertyAccessorHelper.set(entityObject, (Field) column.getJavaMember(), new HashSet(
collectionValues));
break;
case LIST:
PropertyAccessorHelper.set(entityObject, (Field) column.getJavaMember(),
Arrays.asList(((BasicDBList) value).toArray()));
break;
case POINT:
BasicDBList list = (BasicDBList) value;
Object xObj = list.get(0);
Object yObj = list.get(1);
if (xObj != null && yObj != null)
{
try
{
double x = Double.parseDouble(xObj.toString());
double y = Double.parseDouble(yObj.toString());
Point point = new Point(x, y);
PropertyAccessorHelper.set(entityObject, (Field) column.getJavaMember(), point);
}
catch (NumberFormatException e)
{
log.error(
"Error while reading geolocation data for column {} ; Reason - possible corrupt data, Caused by : .",
column, e);
throw new EntityReaderException("Error while reading geolocation data for column " + column
+ "; Reason - possible corrupt data.", e);
}
}
break;
case ENUM:
EnumAccessor accessor = new EnumAccessor();
value = accessor.fromString(javaType, value.toString());
PropertyAccessorHelper.set(entityObject, (Field) column.getJavaMember(), value);
break;
case PRIMITIVE:
value = MongoDBUtils.populateValue(value, value.getClass());
value = MongoDBUtils.getTranslatedObject(value, value.getClass(), javaType);
PropertyAccessorHelper.set(entityObject, (Field) column.getJavaMember(), value);
break;
}
}
catch (PropertyAccessException paex)
{
log.error("Error while setting column {} value, caused by : .",
((AbstractAttribute) column).getJPAColumnName(), paex);
throw new PersistenceException(paex);
}
}
}
/**
* Extract entity field.
*
* @param entity
* the entity
* @param dbObj
* the db obj
* @param column
* the column
* @throws PropertyAccessException
* the property access exception
*/
static void extractFieldValue(Object entity, DBObject dbObj, Attribute column) throws PropertyAccessException
{
try
{
Object valueObject = PropertyAccessorHelper.getObject(entity, (Field) column.getJavaMember());
if (valueObject != null)
{
Class javaType = column.getJavaType();
switch (AttributeType.getType(javaType))
{
case MAP:
Map mapObj = (Map) valueObject;
// BasicDBObjectBuilder builder =
// BasicDBObjectBuilder.start(mapObj);
BasicDBObjectBuilder b = new BasicDBObjectBuilder();
Iterator i = mapObj.entrySet().iterator();
while (i.hasNext())
{
Map.Entry entry = (Map.Entry) i.next();
b.add(entry.getKey().toString(),
MongoDBUtils.populateValue(entry.getValue(), entry.getValue().getClass()));
}
dbObj.put(((AbstractAttribute) column).getJPAColumnName(), b.get());
break;
case SET:
case LIST:
Collection collection = (Collection) valueObject;
BasicDBList basicDBList = new BasicDBList();
for (Object o : collection)
{
basicDBList.add(o);
}
dbObj.put(((AbstractAttribute) column).getJPAColumnName(), basicDBList);
break;
case POINT:
Point p = (Point) valueObject;
double[] coordinate = new double[] { p.getX(), p.getY() };
dbObj.put(((AbstractAttribute) column).getJPAColumnName(), coordinate);
break;
case ENUM:
case PRIMITIVE:
dbObj.put(((AbstractAttribute) column).getJPAColumnName(),
MongoDBUtils.populateValue(valueObject, javaType));
break;
}
}
}
catch (PropertyAccessException paex)
{
log.error("Error while getting column {} value, caused by : .",
((AbstractAttribute) column).getJPAColumnName(), paex);
throw new PersistenceException(paex);
}
}
/**
* Creates a collection of <code>embeddedObjectClass</code> instances
* wherein each element is java object representation of MongoDB document
* object contained in <code>documentList</code>. Field names are determined
* from <code>columns</code>.
*
* @param documentList
* the document list
* @param embeddedCollectionClass
* the embedded collection class
* @param embeddedObjectClass
* the embedded object class
* @param columns
* the columns
* @param metamodel
* @return the collection from document list
*/
static Collection<?> getCollectionFromDocumentList(Metamodel metamodel, BasicDBList documentList,
Class embeddedCollectionClass, Class embeddedObjectClass, Set<Attribute> columns)
{
Collection<Object> embeddedCollection = null;
if (embeddedCollectionClass.equals(Set.class))
{
embeddedCollection = new HashSet<Object>();
}
else if (embeddedCollectionClass.equals(List.class))
{
embeddedCollection = new ArrayList<Object>();
}
else
{
throw new PersistenceException("Invalid collection class " + embeddedCollectionClass
+ "; only Set and List allowed");
}
for (Object dbObj : documentList)
{
try
{
Object obj = embeddedObjectClass.newInstance();
embeddedCollection.add(getObjectFromDocument(metamodel, (BasicDBObject) dbObj, columns, obj));
}
catch (InstantiationException e)
{
throw new PersistenceException(e);
}
catch (IllegalAccessException e)
{
throw new PersistenceException(e);
}
}
return embeddedCollection;
}
}