/**
 * Copyright 2005-2013 Restlet S.A.S.
 * 
 * The contents of this file are subject to the terms of one of the following
 * open source licenses: Apache 2.0 or LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL
 * 1.0 (the "Licenses"). You can select the license that you prefer but you may
 * not use this file except in compliance with one of these Licenses.
 * 
 * You can obtain a copy of the Apache 2.0 license at
 * http://www.opensource.org/licenses/apache-2.0
 * 
 * You can obtain a copy of the LGPL 3.0 license at
 * http://www.opensource.org/licenses/lgpl-3.0
 * 
 * You can obtain a copy of the LGPL 2.1 license at
 * http://www.opensource.org/licenses/lgpl-2.1
 * 
 * You can obtain a copy of the CDDL 1.0 license at
 * http://www.opensource.org/licenses/cddl1
 * 
 * You can obtain a copy of the EPL 1.0 license at
 * http://www.opensource.org/licenses/eclipse-1.0
 * 
 * See the Licenses for the specific language governing permissions and
 * limitations under the Licenses.
 * 
 * Alternatively, you can obtain a royalty free commercial license with less
 * limitations, transferable or non-transferable, directly at
 * http://www.restlet.com/products/restlet-framework
 * 
 * Restlet is a registered trademark of Restlet S.A.S.
 */

package org.restlet.ext.odata.internal;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.restlet.Context;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.ext.atom.Content;
import org.restlet.ext.atom.Entry;
import org.restlet.ext.atom.EntryReader;
import org.restlet.ext.atom.Feed;
import org.restlet.ext.atom.Link;
import org.restlet.ext.atom.Person;
import org.restlet.ext.atom.Relation;
import org.restlet.ext.odata.Service;
import org.restlet.ext.odata.internal.edm.AssociationEnd;
import org.restlet.ext.odata.internal.edm.EntityType;
import org.restlet.ext.odata.internal.edm.Mapping;
import org.restlet.ext.odata.internal.edm.Metadata;
import org.restlet.ext.odata.internal.edm.Property;
import org.restlet.ext.odata.internal.reflect.ReflectUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * Content handler for Atom Feed that takes care of OData specific needs, such
 * as parsing XML content from other namespaces than Atom. It generates an
 * entity based on the values discovered in the entry.
 * 
 * @author Thierry Boileau
 * @param <T>
 *            The type of the parsed entity.
 */
public class EntryContentHandler<T> extends EntryReader {

    private enum State {
        ASSOCIATION, CONTENT, ENTRY, PROPERTIES, PROPERTY
    }

    /** The currently parsed association. */
    private AssociationEnd association;

    /** The path of the current XML element relatively to the Entry. */
    private List<String> eltPath;

    /** The entity targeted by this entry. */
    private T entity;

    /** The class of the entity targeted by this entry. */
    private Class<?> entityClass;

    /** The OData type of the parsed entity. */
    private EntityType entityType;

    /** The currently parsed inline content. */
    private Content inlineContent;

    /** The currently parsed inline entry. */
    private Entry inlineEntry;

    /** Used to parsed Atom link elements that contains entries. */
    private EntryContentHandler<T> inlineEntryHandler;

    /** The currently parsed inline feed. */
    private Feed inlineFeed;

    /** Used to parsed Atom link elements that contains feeds. */
    private FeedContentHandler<T> inlineFeedHandler;

    /** The currently parsed inline link. */
    private Link inlineLink;

    /** Internal logger. */
    private Logger logger;

    /** The currently parsed OData mapping. */
    private Mapping mapping;

    /** The metadata of the WCF service. */
    private Metadata metadata;

    /** Must the current property be set to null? */
    private boolean parsePropertyNull;

    /** Used to handle complex types. */
    private List<String> propertyPath;

    /** Gleans text content. */
    private StringBuilder sb = null;

    /** Heap of states. */
    private List<State> states;

    /**
     * Constructor.
     * 
     * @param entityClass
     *            The class of the parsed entities.
     * @param entityType
     *            The entity type of the parsed entities.
     * @param metadata
     *            The metadata of the remote OData service.
     * @param logger
     *            The logger.
     */
    public EntryContentHandler(Class<?> entityClass, EntityType entityType,
            Metadata metadata, Logger logger) {
        this.entityClass = entityClass;
        this.entityType = entityType;
        this.metadata = metadata;
        this.logger = logger;
    }

    /**
     * Constructor.
     * 
     * @param entityClass
     *            The class of the parsed entities.
     * @param metadata
     *            The metadata of the remote OData service.
     * @param logger
     *            The logger.
     */
    public EntryContentHandler(Class<?> entityClass, Metadata metadata,
            Logger logger) {
        super();
        this.entityClass = entityClass;
        this.entityType = metadata.getEntityType(entityClass);
        this.metadata = metadata;
        this.logger = logger;
    }

    @Override
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        if (State.ASSOCIATION == getState()) {
            // Delegates to the inline content handler
            if (association.isToMany()) {
                inlineFeedHandler.characters(ch, start, length);
            } else {
                inlineEntryHandler.characters(ch, start, length);
            }
        } else if (State.PROPERTY == getState() || mapping != null) {
            sb.append(ch, start, length);
        }
    }

    /**
     * Close the given entry, in case we really handle an entry (@see current
     * {@link State}). Multiple levels expand fix start.
     * 
     * @param entry
     *            The entry to close.
     * @return True if the entry has been closed.
     * @author Emmanuel Liossis
     */
    public boolean closeEntry(Entry entry) {
        boolean result = false;
        if (getState() == State.ENTRY) {
            endEntry(entry);
            result = true;
        }

        return result;
    }

    /**
     * Handles the end of a "link" element. Doesn't pop state if the link
     * belongs to inner expanded associations!
     * 
     * @return True if the caller should not pop state.
     * @author Emmanuel Liossis
     */
    public boolean closeLink() {
        if (State.ASSOCIATION == getState() && inlineLink != null) {
            String propertyName = ReflectUtils.normalize(inlineLink.getTitle());

            if (association.isToMany()) {
                if (inlineFeedHandler.closeLink()) {
                    return true;
                }

                try {
                    ReflectUtils.setProperty(entity, propertyName, association
                            .isToMany(), inlineFeedHandler.getEntities()
                            .iterator(), ReflectUtils.getSimpleClass(entity,
                            propertyName));
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set " + propertyName + " property on "
                                    + entity + " from link");
                }
                inlineFeedHandler = null;
            } else {
                if (inlineEntryHandler.closeLink()) {
                    return true;
                }

                try {
                    ReflectUtils.invokeSetter(entity, propertyName,
                            inlineEntryHandler.getEntity());
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set " + propertyName + " property on "
                                    + entity + " from link");
                }
                inlineEntryHandler = null;
            }

            popState();
            association = null;

            return true;
        }

        // Emmanuel Liossis: So, here we are not in ASSOCIATION state. Should
        // we signal our callers to pop state? There are three cases. First,
        // this can be the end of an inner reference link which doesn't open an
        // association. The state in this case is typically ENTRY. In this case
        // we should return true to inhibit our caller to pop state, because
        // this isn't the association's end. Second, this might be a content of
        // a non-expanded association. In this case the state is null because it
        // hasn't found any 'entry' tag. The association state of the caller
        // must be closed, so we return false. Third, this can be the end of an
        // expanded, in-lined association. In this case the state is again null
        // because because of previously handling the end of the 'entry' tag.
        // So, all the above boils down to this simple rule:
        return getState() != null;
    }

    @Override
    public void endContent(Content content) {
        if (State.ASSOCIATION == getState()) {
            // Delegates to the inline content handler
            if (association.isToMany()) {
                inlineFeedHandler.endContent(content);
            } else {
                inlineEntryHandler.endContent(content);
            }
        } else {
            // Emmanuel Liossis: Multiple levels expand: Design flaw makes
            // O(d^2) calls to inner expansions, where d is expansion depth .
            // Don't pop many times for a single content close.
            if (State.CONTENT == getState()) {
                popState();
            }
        }
    }

    // Emmanuel Liossis: Multiple levels expand fix.
    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        if (State.ASSOCIATION == getState()) {
            // Delegates to the inline content handler
            if (uri.equalsIgnoreCase(Feed.ATOM_NAMESPACE)) {
                if (localName.equals("feed")) {
                    if (association.isToMany()) {
                        inlineFeedHandler.endFeed(inlineFeed);
                    }
                } else if (localName.equals("link")) {
                    if (association.isToMany()) {
                        inlineFeedHandler.closeLink();
                    } else {
                        inlineEntryHandler.closeLink();
                    }
                    // Emmanuel Liossis: Don't propagate the endElement call,
                    // because it would re-invoke closeLink if the expansion
                    // level is deep.
                    return;
                } else if (localName.equalsIgnoreCase("entry")) {
                    if (association.isToMany()) {
                        inlineFeedHandler.endEntry(inlineEntry);
                    } else {
                        inlineEntryHandler.closeEntry(inlineEntry);
                    }
                } else if (localName.equalsIgnoreCase("content")) {
                    if (association.isToMany()) {
                        inlineFeedHandler.endContent(inlineContent);
                    } else {
                        inlineEntryHandler.endContent(inlineContent);
                    }
                }
            }

            if (association.isToMany()) {
                inlineFeedHandler.endElement(uri, localName, qName);
            } else {
                inlineEntryHandler.endElement(uri, localName, qName);
            }
        } else if (State.PROPERTY == getState()) {
            if (parsePropertyNull) {
                popState();
                parsePropertyNull = false;
            } else {
                Object obj = entity;
                if (propertyPath.size() > 1) {
                    // Complex property.
                    for (int i = 0; i < propertyPath.size() - 1; i++) {
                        try {
                            Object o = ReflectUtils.invokeGetter(obj,
                                    propertyPath.get(i));
                            if (o == null) {
                                // Try to instantiate it
                                Field[] fields = obj.getClass()
                                        .getDeclaredFields();
                                for (Field field : fields) {
                                    if (field.getName().equalsIgnoreCase(
                                            propertyPath.get(i))) {
                                        o = field.getType().newInstance();
                                        break;
                                    }
                                }
                            }
                            ReflectUtils.invokeSetter(obj, propertyPath.get(i),
                                    o);
                            obj = o;
                        } catch (Exception e) {
                            obj = null;
                        }
                    }
                }
                Property property = metadata.getProperty(obj, localName);
                if (property != null) {
                    try {
                        ReflectUtils.setProperty(obj, property, sb.toString());
                    } catch (Exception e) {
                        getLogger().warning(
                                "Cannot set " + localName + " property on "
                                        + obj + " with value " + sb.toString());
                    }
                }
                if (!propertyPath.isEmpty()) {
                    propertyPath.remove(propertyPath.size() - 1);
                }
                if (propertyPath.isEmpty()) {
                    // There is only one state for parsing complex or simple
                    // properties.
                    popState();
                }
            }
        } else if (State.PROPERTIES == getState()) {
            popState();
        } else if (State.CONTENT == getState()) {
            popState();
        } else if (mapping != null) {
            // A mapping has been discovered
            if (sb != null) {
                try {
                    ReflectUtils.invokeSetter(entity,
                            mapping.getPropertyPath(), sb.toString());
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set the mapped property "
                                    + mapping.getPropertyPath() + " on "
                                    + entity + " with value " + sb.toString());
                }
            }
            mapping = null;
        } else if (State.ENTRY == getState()) {
            if (!eltPath.isEmpty()) {
                eltPath.remove(eltPath.size() - 1);
            }
        }
    }

    @Override
    public void endEntry(Entry entry) {

        this.states = new ArrayList<State>();

        // Handle Atom mapped values.
        for (Mapping m : metadata.getMappings()) {
            if (entityType != null && entityType.equals(m.getType())
                    && m.getNsUri() == null && m.getNsPrefix() == null) {
                // mapping atom
                Person author = (entry.getAuthors().isEmpty()) ? null : entry
                        .getAuthors().get(0);
                Person contributor = (entry.getContributors().isEmpty()) ? null
                        : entry.getContributors().get(0);
                Object value = null;
                if ("SyndicationAuthorEmail".equals(m.getValuePath())) {
                    value = (author != null) ? author.getEmail() : null;
                } else if ("SyndicationAuthorName".equals(m.getValuePath())) {
                    value = (author != null) ? author.getName() : null;
                } else if ("SyndicationAuthorUri".equals(m.getValuePath())) {
                    value = (author != null) ? author.getUri().toString()
                            : null;
                } else if ("SyndicationContributorEmail".equals(m
                        .getValuePath())) {
                    value = (contributor != null) ? contributor.getEmail()
                            : null;
                } else if ("SyndicationContributorName"
                        .equals(m.getValuePath())) {
                    value = (contributor != null) ? contributor.getName()
                            : null;
                } else if ("SyndicationContributorUri".equals(m.getValuePath())) {
                    value = (contributor != null) ? contributor.getUri()
                            .toString() : null;
                } else if ("SyndicationPublished".equals(m.getValuePath())) {
                    value = entry.getPublished();
                } else if ("SyndicationRights".equals(m.getValuePath())) {
                    value = (entry.getRights() != null) ? entry.getRights()
                            .getContent() : null;
                } else if ("SyndicationSummary".equals(m.getValuePath())) {
                    value = entry.getSummary();
                } else if ("SyndicationTitle".equals(m.getValuePath())) {
                    value = (entry.getTitle() != null) ? entry.getTitle()
                            .getContent() : null;
                } else if ("SyndicationUpdated".equals(m.getValuePath())) {
                    value = entry.getUpdated();
                }

                try {
                    if (value != null) {
                        ReflectUtils.invokeSetter(entity, m.getPropertyPath(),
                                value);
                    }
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set " + m.getPropertyPath()
                                    + " property on " + entity + " with value "
                                    + value);
                }
            }
        }

        // If the entity is a blob, get the edit reference
        if (entityType != null && entityType.isBlob()
                && entityType.getBlobValueEditRefProperty() != null) {
            // Look for en entry with a "edit-media" relation value.
            Link link = entry.getLink(Relation.EDIT_MEDIA);
            String pty = entityType.getBlobValueEditRefProperty().getName();
            if (link != null) {
                try {
                    ReflectUtils.invokeSetter(entity, pty, link.getHref());
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set the property " + pty + " on " + entity
                                    + " with value " + link.getHref());
                }
            }
        }
    }

    // Emmanuel Liossis: Fix for multiple level expansions: This method is
    // replaced with the above one.
    @Override
    public void endLink(Link link) {
        if (State.ASSOCIATION == getState()) {
            String propertyName = ReflectUtils.normalize(link.getTitle());

            if (association.isToMany()) {
                inlineFeedHandler.endLink(link);

                try {
                    ReflectUtils.setProperty(entity, propertyName, association
                            .isToMany(), inlineFeedHandler.getEntities()
                            .iterator(), ReflectUtils.getSimpleClass(entity,
                            propertyName));
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set " + propertyName + " property on "
                                    + entity + " from link");
                }
                inlineFeedHandler = null;
            } else {
                inlineEntryHandler.endLink(link);

                try {
                    ReflectUtils.invokeSetter(entity, propertyName,
                            inlineEntryHandler.getEntity());
                } catch (Exception e) {
                    getLogger().warning(
                            "Cannot set " + propertyName + " property on "
                                    + entity + " from link");
                }
                inlineEntryHandler = null;
            }

            // This works if the inline entries does not contain links as
            // well...
            // Emmanuel Liossis: Indeed the above is true. It can sabotage
            // expansions with depth more than 1. That's why this method is
            // replaced by closeLink method.
            popState();
            association = null;
        }
    }

    public T getEntity() {
        return entity;
    }

    /**
     * Returns the current logger.
     * 
     * @return The current logger.
     */
    private Logger getLogger() {
        if (logger == null) {
            logger = Context.getCurrentLogger();
        }
        return logger;
    }

    /**
     * Returns a media type from an Atom type attribute.
     * 
     * @param type
     *            The Atom type attribute.
     * @return The media type.
     */
    private MediaType getMediaType(String type) {
        MediaType result = null;

        if (type == null) {
            // No type defined
        } else if (type.equals("text")) {
            result = MediaType.TEXT_PLAIN;
        } else if (type.equals("html")) {
            result = MediaType.TEXT_HTML;
        } else if (type.equals("xhtml")) {
            result = MediaType.APPLICATION_XHTML;
        } else {
            result = new MediaType(type);
        }

        return result;
    }

    /**
     * Returns the state at the top of the heap.
     * 
     * @return The state at the top of the heap.
     */
    private State getState() {
        State result = null;
        if (this.states != null) {
            int size = this.states.size();
            if (size > 0) {
                result = this.states.get(size - 1);
            }
        }
        return result;
    }

    /**
     * Returns the state at the top of the heap and removes it from the heap.
     * 
     * @return The state at the top of the heap.
     */
    private State popState() {
        State result = null;
        int size = this.states.size();
        if (size > 0) {
            result = this.states.remove(size - 1);
        }

        return result;
    }

    /**
     * Adds a new state at the top of the heap.
     * 
     * @param state
     *            The state to add.
     */
    private void pushState(State state) {
        this.states.add(state);
    }

    @Override
    public void startContent(Content content) {
        if (State.ENTRY == getState()) {
            pushState(State.CONTENT);
            inlineContent = content;
            if (entityType != null && entityType.isBlob()
                    && entityType.getBlobValueRefProperty() != null) {
                Reference ref = content.getExternalRef();
                if (ref != null) {
                    try {
                        ReflectUtils.invokeSetter(entity, entityType
                                .getBlobValueRefProperty().getName(), ref);
                    } catch (Exception e) {
                        getLogger().warning(
                                "Cannot set "
                                        + entityType.getBlobValueRefProperty()
                                                .getName() + " property on "
                                        + entity + " with value " + ref);
                    }
                }
            }

        }

        else if (State.ASSOCIATION == getState()) {
            if (association.isToMany())
                inlineFeedHandler.startContent(content);
            else
                inlineEntryHandler.startContent(content);
        }
    }

    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes attrs) throws SAXException {
        if (State.ASSOCIATION == getState()) {
            // Delegates to the inline content handler
            if (uri.equalsIgnoreCase(Feed.ATOM_NAMESPACE)) {
                if (localName.equals("feed")) {
                    Feed feed = new Feed();
                    String attr = attrs.getValue("xml:base");
                    if (attr != null) {
                        feed.setBaseReference(new Reference(attr));
                    }

                    if (association.isToMany())
                        this.inlineFeedHandler.startFeed(feed);
                    else
                        this.inlineEntryHandler.startFeed(feed);
                } else if (localName.equals("link")) {
                    Link link = new Link();
                    link.setHref(new Reference(attrs.getValue("", "href")));
                    link.setRel(Relation.valueOf(attrs.getValue("", "rel")));
                    String type = attrs.getValue("", "type");
                    if (type != null && type.length() > 0) {
                        link.setType(new MediaType(type));
                    }

                    link.setHrefLang(new Language(attrs
                            .getValue("", "hreflang")));
                    link.setTitle(attrs.getValue("", "title"));
                    String attr = attrs.getValue("", "length");
                    link.setLength((attr == null) ? -1L : Long.parseLong(attr));

                    if (association.isToMany()) {
                        inlineFeedHandler.startLink(link);
                    } else {
                        inlineEntryHandler.startLink(link);
                    }
                } else if (localName.equalsIgnoreCase("entry")) {
                    Entry entry = new Entry();
                    if (association.isToMany()) {
                        inlineFeedHandler.startEntry(entry);
                    } else {
                        inlineEntryHandler.startEntry(entry);
                    }
                    inlineEntry = entry;
                } else if (localName.equalsIgnoreCase("content")) {
                    Content content = new Content();
                    MediaType type = getMediaType(attrs.getValue("", "type"));
                    String srcAttr = attrs.getValue("", "src");
                    if (srcAttr != null)
                        // Content available externally
                        content.setExternalRef(new Reference(srcAttr));
                    content.setExternalType(type);
                    if (association.isToMany()) {
                        inlineFeedHandler.startContent(content);
                    } else {
                        inlineEntryHandler.startContent(content);
                    }
                }
            }

            if (association.isToMany()) {
                inlineFeedHandler.startElement(uri, localName, qName, attrs);
            } else {
                inlineEntryHandler.startElement(uri, localName, qName, attrs);
            }
        } else if (Service.WCF_DATASERVICES_METADATA_NAMESPACE.equals(uri)
                && "properties".equals(localName)) {
            pushState(State.PROPERTIES);
            propertyPath = new ArrayList<String>();
        } else if (State.PROPERTIES == getState()) {
            pushState(State.PROPERTY);

            if (Boolean.parseBoolean(attrs.getValue(
                    Service.WCF_DATASERVICES_METADATA_NAMESPACE, "null"))) {
                parsePropertyNull = true;
            } else {
                sb = new StringBuilder();
                propertyPath.add(localName);
            }
        } else if (State.PROPERTY == getState()) {
            sb = new StringBuilder();
            propertyPath.add(localName);
        } else if (State.ENTRY == getState()) {
            if (localName.equalsIgnoreCase("link") && association != null) {
                pushState(State.ASSOCIATION);
            } else {
                // Could be mapped value
                eltPath.add(localName);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < eltPath.size(); i++) {
                    if (i > 0) {
                        sb.append("/");
                    }
                    sb.append(eltPath.get(i));
                }
                String str = sb.toString();

                // Check if this path is mapped.
                for (Mapping m : metadata.getMappings()) {
                    if (entityType != null
                            && entityType.equals(m.getType())
                            && m.getNsUri() != null
                            && m.getNsUri().equals(uri)
                            && (str.equals(m.getValueNodePath()) || str
                                    .equals("entry/" + m.getValueNodePath()))) {
                        if (m.isAttributeValue()) {
                            String value = attrs.getValue(m
                                    .getValueAttributeName());
                            if (value != null) {
                                try {
                                    ReflectUtils.invokeSetter(entity,
                                            m.getPropertyPath(), value);
                                } catch (Exception e) {
                                    getLogger().warning(
                                            "Cannot set " + m.getPropertyPath()
                                                    + " property on " + entity
                                                    + " with value " + value);
                                }
                            }
                        } else {
                            this.sb = new StringBuilder();
                            mapping = m;
                        }
                        break;
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void startEntry(Entry entry) {

        if (getState() != null) {
            switch (getState()) {
            case ASSOCIATION:
                if (association.isToMany())
                    inlineFeedHandler.startEntry(entry);
                else
                    inlineEntryHandler.startEntry(entry);
                break;
            }
        } else {
            this.states = new ArrayList<State>();
            pushState(State.ENTRY);
            eltPath = new ArrayList<String>();
            // Instantiate the entity
            try {
                entity = (T) entityClass.newInstance();
            } catch (Exception e) {
                getLogger().warning(
                        "Error when instantiating  class " + entityClass);
            }

        }
    }

    public void startFeed(Feed feed) {
        if (State.ASSOCIATION == getState()) {
            if (association.isToMany())
                inlineFeedHandler.startFeed(feed);
            else
                inlineEntryHandler.startFeed(feed);
        }
    }

    @Override
    public void startLink(Link link) {
        if (State.ASSOCIATION == getState()) {
            // Delegates to the inline content handler
            if (association.isToMany()) {
                inlineFeedHandler.startLink(link);
            } else {
                inlineEntryHandler.startLink(link);
            }
        } else {
            if (link.getTitle() != null && entityType != null) {
                String propertyName = ReflectUtils.normalize(link.getTitle());
                // Get the associated entity
                association = metadata.getAssociation(entityType, propertyName);
                if (association != null) {
                    inlineLink = link;
                    if (association.isToMany()) {
                        inlineFeedHandler = new FeedContentHandler<T>(
                                ReflectUtils.getSimpleClass(entity,
                                        propertyName), metadata, getLogger());
                    } else {
                        inlineEntryHandler = new EntryContentHandler<T>(
                                ReflectUtils.getSimpleClass(entity,
                                        propertyName), metadata, getLogger());
                    }
                }
            }
        }
    }
}
