com.haulmont.cuba.restapi.XMLConverter2.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.restapi.XMLConverter2.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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.haulmont.cuba.restapi;

import com.haulmont.bali.util.Dom4j;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.chile.core.datatypes.Datatype;
import com.haulmont.chile.core.datatypes.Datatypes;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.MetaPropertyPath;
import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesUtils;
import com.haulmont.cuba.core.entity.BaseEntityInternalAccess;
import com.haulmont.cuba.core.entity.BaseGenericIdEntity;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.SecurityState;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.security.entity.EntityAttrAccess;
import com.haulmont.cuba.security.entity.EntityOp;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import javax.activation.MimeType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.StringWriter;
import java.text.ParseException;
import java.util.*;

/**
 * XML Converter that works with new xml schema defined in platform v5.4
 *
 */
@Component
public class XMLConverter2 implements Converter {

    public static final MimeType MIME_TYPE_XML;
    public static final String MIME_STR = "text/xml;charset=UTF-8";
    public static final String TYPE_XML = "xml";

    static {
        try {
            MIME_TYPE_XML = new MimeType(MIME_STR);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Inject
    protected Metadata metadata;

    @Inject
    protected MetadataTools metadataTools;

    @Inject
    protected Configuration configuration;

    protected RestConfig restConfig;

    @PostConstruct
    public void init() {
        restConfig = configuration.getConfig(RestConfig.class);
    }

    @Override
    public MimeType getMimeType() {
        return MIME_TYPE_XML;
    }

    @Override
    public String getType() {
        return TYPE_XML;
    }

    @Override
    public List<Integer> getApiVersions() {
        return Arrays.asList(1, 2);
    }

    @Override
    public String process(Entity entity, MetaClass metaclass, View view) throws Exception {
        Document document = _process(entity, view);
        return documentToString(document);
    }

    protected Document _process(Entity entity, View view) throws Exception {
        Document document = DocumentHelper.createDocument();
        Element rootEl = document.addElement("instances");
        encodeEntity(entity, new HashSet<Entity>(), view, rootEl);
        return document;
    }

    @Override
    public String process(List<Entity> entities, MetaClass metaClass, View view) throws Exception {
        Document document = _process(entities, metaClass, view);
        return documentToString(document);
    }

    protected Document _process(List<Entity> entities, MetaClass metaClass, View view) throws Exception {
        Document document = DocumentHelper.createDocument();
        Element rootEl = document.addElement("instances");
        for (Entity entity : entities) {
            encodeEntity(entity, new HashSet<Entity>(), view, rootEl);
        }
        return document;
    }

    @Override
    public String process(Set<Entity> entities) throws Exception {
        Document document = DocumentHelper.createDocument();
        Element rootEl = document.addElement("instances");
        for (Entity entity : entities) {
            encodeEntity(entity, new HashSet<Entity>(), null, rootEl);
        }
        return documentToString(document);
    }

    @Override
    @Nonnull
    public String processServiceMethodResult(Object result, Class resultType) throws Exception {
        Document document = DocumentHelper.createDocument();
        Element resultEl = document.addElement("result");
        if (result instanceof Entity) {
            Entity entity = (Entity) result;
            Document convertedEntity = _process(entity, null);
            resultEl.add(convertedEntity.getRootElement());
        } else if (result instanceof Collection) {
            if (!checkCollectionItemTypes((Collection) result, Entity.class))
                throw new IllegalArgumentException(
                        "Items that are not instances of Entity class found in service method result");
            ArrayList list = new ArrayList((Collection) result);
            MetaClass metaClass = null;
            if (!list.isEmpty())
                metaClass = ((Entity) list.get(0)).getMetaClass();

            Document processed = _process(list, metaClass, null);
            resultEl.add(processed.getRootElement());
        } else {
            if (result != null && resultType != Void.TYPE) {
                Datatype datatype = getDatatype(resultType);
                resultEl.setText(datatype != null ? datatype.format(result) : result.toString());
            } else {
                encodeNull(resultEl);
            }
        }
        return documentToString(document);
    }

    protected Datatype getDatatype(Class clazz) {
        if (clazz == Integer.TYPE || clazz == Byte.TYPE || clazz == Short.TYPE)
            return Datatypes.get(Integer.class);
        if (clazz == Long.TYPE)
            return Datatypes.get(Long.class);
        if (clazz == Boolean.TYPE)
            return Datatypes.get(Boolean.class);

        return Datatypes.get(clazz);
    }

    protected boolean checkCollectionItemTypes(Collection collection, Class<?> itemClass) {
        for (Object collectionItem : collection) {
            if (!itemClass.isAssignableFrom(collectionItem.getClass()))
                return false;
        }
        return true;
    }

    @Override
    public CommitRequest parseCommitRequest(String content) {
        try {
            CommitRequest commitRequest = new CommitRequest();

            Document document = Dom4j.readDocument(content);
            Element rootElement = document.getRootElement();

            //commit instances
            Element commitInstancesEl = rootElement.element("commitInstances");
            if (commitInstancesEl != null) {
                //first find and register ids of all entities to be commited
                Set<String> commitIds = new HashSet<>();
                for (Object instance : commitInstancesEl.elements("instance")) {
                    Element instanceEl = (Element) instance;
                    String id = instanceEl.attributeValue("id");
                    if (id.startsWith("NEW-"))
                        id = id.substring(id.indexOf('-') + 1);
                    commitIds.add(id);
                }
                commitRequest.setCommitIds(commitIds);

                List commitInstanceElements = commitInstancesEl.elements("instance");
                List<Entity> commitInstances = new ArrayList<>();
                for (Object el : commitInstanceElements) {
                    Element commitInstanceEl = (Element) el;
                    String id = commitInstanceEl.attributeValue("id");
                    InstanceRef ref = commitRequest.parseInstanceRefAndRegister(id);
                    Entity instance = ref.getInstance();
                    parseEntity(commitInstanceEl, instance, commitRequest);
                    commitInstances.add(instance);
                }
                commitRequest.setCommitInstances(commitInstances);
            }

            //remove instances
            Element removeInstancesEl = rootElement.element("removeInstances");
            if (removeInstancesEl != null) {
                List removeInstanceElements = removeInstancesEl.elements("instance");
                List<Entity> removeInstances = new ArrayList<>();
                for (Object el : removeInstanceElements) {
                    Element removeInstance = (Element) el;
                    String id = removeInstance.attributeValue("id");
                    InstanceRef ref = commitRequest.parseInstanceRefAndRegister(id);
                    Entity instance = ref.getInstance();
                    removeInstances.add(instance);
                }
                commitRequest.setRemoveInstances(removeInstances);
            }

            //soft deletion
            Element softDeletionEl = rootElement.element("softDeletion");
            if (softDeletionEl != null) {
                commitRequest.setSoftDeletion(Boolean.parseBoolean(softDeletionEl.getText()));
            }

            return commitRequest;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public ServiceRequest parseServiceRequest(String content) throws Exception {
        Document document = Dom4j.readDocument(content);
        Element rootElement = document.getRootElement();
        Element serviceEl = rootElement.element("service");
        if (serviceEl == null) {
            throw new IllegalArgumentException("Service name not specified in request");
        }
        String serviceName = serviceEl.getTextTrim();
        Element methodEl = rootElement.element("method");
        if (methodEl == null) {
            throw new IllegalArgumentException("Method name not specified in request");
        }
        String methodName = methodEl.getTextTrim();
        String viewName = null;
        Element viewEl = rootElement.element("view");
        if (viewEl != null)
            viewName = viewEl.getTextTrim();

        ServiceRequest serviceRequest = new ServiceRequest(serviceName, methodName, this);

        Element paramsEl = rootElement.element("params");
        if (paramsEl != null) {
            int idx = 0;
            while (true) {
                String paramName = "param" + idx;
                Element paramEl = findParamByName(paramsEl, paramName);
                if (paramEl == null)
                    break;
                serviceRequest.getParamValuesString().add(paramElementContentAsString(paramEl));

                Element paramTypeEl = findParamByName(paramsEl, paramName + "_type");
                if (paramTypeEl != null) {
                    String type = paramTypeEl.getText();
                    serviceRequest.getParamTypes().add(ClassUtils.forName(type, null));
                } else {
                    if (!serviceRequest.getParamTypes().isEmpty()) {
                        //types should be defined for all parameters or for none of them
                        throw new RestServiceException("Parameter type for param" + idx + " is not defined");
                    }
                }
                idx++;
            }
        }

        return serviceRequest;
    }

    protected Element findParamByName(Element paramsEl, String paramName) {
        List params = paramsEl.elements("param");
        for (Object param : params) {
            String curName = ((Element) param).attributeValue("name");
            if (paramName.equals(curName))
                return (Element) param;
        }
        return null;
    }

    protected String paramElementContentAsString(Element paramEl) {
        Element nestedEl = paramEl.element("instance");
        if (nestedEl == null) {
            nestedEl = paramEl.element("instances");
        }

        if (nestedEl == null) {
            return paramEl.getText();
        } else {
            Document doc = DocumentHelper.createDocument(nestedEl.createCopy());
            return doc.asXML();
        }
    }

    @Override
    public Entity parseEntity(String content) {
        Document document = Dom4j.readDocument(content);
        Element instanceEl = document.getRootElement();
        return parseEntity(instanceEl, null, null);
    }

    @Override
    public QueryRequest parseQueryRequest(String content)
            throws IllegalArgumentException, ClassNotFoundException, ParseException {
        Document document = Dom4j.readDocument(content);
        Element rootElement = document.getRootElement();
        QueryRequest queryRequest = new QueryRequest();

        Element entityElem = rootElement.element("entity");
        String entity = entityElem.getTextTrim();
        queryRequest.setEntity(entity);

        Element queryElem = rootElement.element("query");
        String query = queryElem.getTextTrim();
        queryRequest.setQuery(query);

        if (rootElement.element("view") != null) {
            Element viewElem = rootElement.element("view");
            String view = viewElem.getTextTrim();
            queryRequest.setViewName(view);
        }

        if (rootElement.element("max") != null) {
            Element maxElem = rootElement.element("max");
            String maxString = maxElem.getTextTrim();
            int max = Integer.parseInt(maxString);
            queryRequest.setMax(max);
        }

        if (rootElement.element("first") != null) {
            Element firstElem = rootElement.element("first");
            String firstString = firstElem.getTextTrim();
            int first = Integer.parseInt(firstString);
            queryRequest.setFirst(first);
        }

        if (rootElement.element("dynamicAttributes") != null) {
            Element dynamicAttributesElem = rootElement.element("dynamicAttributes");
            String dynamicAttributesString = dynamicAttributesElem.getTextTrim();
            Boolean dynamicAttributes = Boolean.valueOf(dynamicAttributesString);
            queryRequest.setDynamicAttributes(dynamicAttributes);
        }

        if (rootElement.element("params") != null) {
            Element paramsElem = rootElement.element("params");
            List paramList = paramsElem.elements("param");
            for (Object obj : paramList) {
                if (obj instanceof Element) {
                    Element paramElem = (Element) obj;

                    Element nameElem = paramElem.element("name");
                    String paramName = nameElem.getStringValue();

                    Element valueElem = paramElem.element("value");
                    String paramValue = valueElem.getStringValue();

                    Object value = null;
                    if (paramElem.element("type") != null) {
                        Element typeElem = paramElem.element("type");
                        String typeString = typeElem.getStringValue();
                        Class type = ClassUtils.forName(typeString, null);
                        value = ParseUtils.toObject(type, paramValue, this);
                    } else {
                        value = ParseUtils.tryParse(paramValue);
                    }

                    queryRequest.getParams().put(paramName, value != null ? value : paramValue);
                }
            }
        }

        return queryRequest;
    }

    @Override
    public Collection<? extends Entity> parseEntitiesCollection(String content,
            Class<? extends Collection> collectionClass) {
        try {
            Collection collection = newCollectionInstance(collectionClass);
            Document document = Dom4j.readDocument(content);
            List instances = document.getRootElement().elements("instance");
            for (Object instance : instances) {
                Entity entity = parseEntity((Element) instance, null, null);
                collection.add(entity);
            }
            return collection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected Collection newCollectionInstance(Class<? extends Collection> clazz)
            throws IllegalAccessException, InstantiationException {
        if (!clazz.isInterface()) {
            return clazz.newInstance();
        } else {
            if (List.class.isAssignableFrom(clazz))
                return new ArrayList();
            if (Set.class.isAssignableFrom(clazz))
                return new HashSet();
        }
        throw new IllegalArgumentException("Collections of type" + clazz.getName() + " not supported");
    }

    /**
     * Converts a content of XML element to an entity.
     *
     * @param instanceEl    element that contains entity description
     * @param entity        if this parameter is not null then its fields will be filled,
     *                      if it is null then new entity will be created.
     * @param commitRequest must not be null if method is called when parsing a {@code CommitRequest}.
     *                      Security permissions checks are performed based on existing/absence of this
     *                      parameter.
     */
    protected Entity parseEntity(Element instanceEl, @Nullable Entity entity,
            @Nullable CommitRequest commitRequest) {
        try {
            if (entity == null) {
                String id = instanceEl.attributeValue("id");
                EntityLoadInfo loadInfo = EntityLoadInfo.parse(id);

                if (loadInfo == null)
                    throw new IllegalArgumentException(
                            "XML description of entity doesn't contain valid 'id' attribute");

                entity = createEmptyInstance(loadInfo);
                entity.setValue("id", loadInfo.getId());
            }

            MetaClass metaClass = entity.getMetaClass();
            List propertyEls = instanceEl.elements();
            for (Object el : propertyEls) {
                Element propertyEl = (Element) el;

                if (entity instanceof BaseGenericIdEntity && "__securityToken".equals(propertyEl.getName())) {
                    byte[] securityToken = Base64.getDecoder().decode(propertyEl.getText());
                    SecurityState securityState = BaseEntityInternalAccess
                            .getOrCreateSecurityState((BaseGenericIdEntity) entity);
                    BaseEntityInternalAccess.setSecurityToken(securityState, securityToken);
                    continue;
                }

                String propertyName = propertyEl.attributeValue("name");

                MetaPropertyPath metaPropertyPath = metadata.getTools().resolveMetaPropertyPath(metaClass,
                        propertyName);
                Preconditions.checkNotNullArgument(metaPropertyPath, "Could not resolve property '%s' in '%s'",
                        propertyName, metaClass);
                MetaProperty property = metaPropertyPath.getMetaProperty();

                if (commitRequest != null && !attrModifyPermitted(metaClass, propertyName))
                    continue;

                if (commitRequest != null && metadataTools.isNotPersistent(property)
                        && !DynamicAttributesUtils.isDynamicAttribute(propertyName))
                    continue;

                if (Boolean.parseBoolean(propertyEl.attributeValue("null"))) {
                    entity.setValue(propertyName, null);
                    continue;
                }

                if (entity instanceof BaseGenericIdEntity && DynamicAttributesUtils.isDynamicAttribute(propertyName)
                        && ((BaseGenericIdEntity) entity).getDynamicAttributes() == null) {
                    ConverterHelper.fetchDynamicAttributes(entity);
                }

                String stringValue = propertyEl.getText();
                Object value;
                switch (property.getType()) {
                case DATATYPE:
                    value = property.getRange().asDatatype().parse(stringValue);
                    entity.setValue(propertyName, value);
                    break;
                case ENUM:
                    value = property.getRange().asEnumeration().parse(stringValue);
                    entity.setValue(propertyName, value);
                    break;
                case COMPOSITION:
                case ASSOCIATION:
                    MetaClass propertyMetaClass = propertyMetaClass(property);
                    //checks if the user permitted to read and update a property
                    if (commitRequest != null && !updatePermitted(propertyMetaClass)
                            && !readPermitted(propertyMetaClass))
                        break;

                    if (!property.getRange().getCardinality().isMany()) {
                        Element refInstanceEl = propertyEl.element("instance");
                        if (metadataTools.isEmbedded(property)) {
                            MetaClass embeddedMetaClass = property.getRange().asClass();
                            Entity embeddedEntity = metadata.create(embeddedMetaClass);
                            value = parseEntity(refInstanceEl, embeddedEntity, commitRequest);
                        } else {
                            String id = refInstanceEl.attributeValue("id");

                            //reference to an entity that also a commit instance
                            //will be registered later
                            if (commitRequest != null && commitRequest.getCommitIds().contains(id)) {
                                EntityLoadInfo loadInfo = EntityLoadInfo.parse(id);
                                Entity ref = metadata.create(loadInfo.getMetaClass());
                                ref.setValue("id", loadInfo.getId());
                                entity.setValue(propertyName, ref);
                                break;
                            }

                            value = parseEntity(refInstanceEl, null, commitRequest);
                        }
                        entity.setValue(propertyName, value);

                    } else {
                        Class<?> propertyJavaType = property.getJavaType();
                        Collection<Object> coll;
                        if (List.class.isAssignableFrom(propertyJavaType))
                            coll = new ArrayList<>();
                        else if (Set.class.isAssignableFrom(propertyJavaType))
                            coll = new HashSet<>();
                        else
                            throw new RuntimeException("Datatype " + propertyJavaType.getName() + " of "
                                    + metaClass.getName() + "#" + property.getName() + " is not supported");
                        entity.setValue(propertyName, coll);

                        for (Object childInstenceEl : propertyEl.elements("instance")) {
                            Entity childEntity = parseEntity((Element) childInstenceEl, null, commitRequest);
                            coll.add(childEntity);
                        }
                    }
                    break;
                default:
                    throw new IllegalStateException("Unknown property type");
                }
            }

            return entity;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Creates new entity instance from {@link com.haulmont.cuba.core.global.EntityLoadInfo}
     * and reset fields values
     */
    protected Entity createEmptyInstance(EntityLoadInfo loadInfo)
            throws IllegalAccessException, InstantiationException {
        MetaClass metaClass = loadInfo.getMetaClass();
        Entity instance = metadata.create(metaClass);
        for (MetaProperty metaProperty : metaClass.getProperties()) {
            if (!metaProperty.isReadOnly())
                instance.setValue(metaProperty.getName(), null);
        }
        return instance;
    }

    protected void encodeEntity(Entity entity, HashSet<Entity> visited, View view, Element parentEl)
            throws Exception {
        if (entity == null) {
            parentEl.addAttribute("null", "true");
            return;
        }

        if (!readPermitted(entity.getMetaClass()))
            throw new IllegalAccessException();

        Element instanceEl = parentEl.addElement("instance");
        instanceEl.addAttribute("id", EntityLoadInfo.create(entity).toString());

        boolean entityAlreadyVisited = !visited.add(entity);
        if (entityAlreadyVisited) {
            return;
        }

        if (entity instanceof BaseGenericIdEntity) {
            byte[] securityToken = BaseEntityInternalAccess.getSecurityToken((BaseGenericIdEntity) entity);

            if (securityToken != null) {
                BaseGenericIdEntity baseGenericIdEntity = (BaseGenericIdEntity) entity;
                instanceEl.addElement("__securityToken").setText(Base64.getEncoder().encodeToString(securityToken));

                String[] filteredAttributes = BaseEntityInternalAccess.getFilteredAttributes(baseGenericIdEntity);

                if (filteredAttributes != null) {
                    Element filteredAttributesElement = instanceEl.addElement("__filteredAttributes");
                    Arrays.stream(filteredAttributes)
                            .forEach(obj -> filteredAttributesElement.addElement("a").setText(obj));
                }
            }
        }

        MetaClass metaClass = entity.getMetaClass();
        List<MetaProperty> orderedProperties = ConverterHelper.getActualMetaProperties(metaClass, entity);
        for (MetaProperty property : orderedProperties) {
            if (metadataTools.isPersistent(property) && !PersistenceHelper.isLoaded(entity, property.getName())) {
                continue;
            }

            if (!attrViewPermitted(metaClass, property.getName()))
                continue;

            if (!isPropertyIncluded(view, property)
                    && !DynamicAttributesUtils.isDynamicAttribute(property.getName())) {
                continue;
            }

            Object value = entity.getValue(property.getName());

            switch (property.getType()) {
            case DATATYPE:
                if (property.equals(metadataTools.getPrimaryKeyProperty(metaClass))
                        && !property.getJavaType().equals(String.class)) {
                    // skipping id for non-String-key entities
                    continue;
                }
                Element fieldEl = instanceEl.addElement("field");
                fieldEl.addAttribute("name", property.getName());
                if (value != null) {
                    fieldEl.setText(property.getRange().asDatatype().format(value));
                } else if (!DynamicAttributesUtils.isDynamicAttribute(property.getName())) {
                    encodeNull(fieldEl);
                }
                break;
            case ENUM:
                fieldEl = instanceEl.addElement("field");
                fieldEl.addAttribute("name", property.getName());
                if (value == null) {
                    encodeNull(fieldEl);
                } else {
                    fieldEl.setText(property.getRange().asEnumeration().format(value));
                }
                break;
            case COMPOSITION:
            case ASSOCIATION:
                MetaClass meta = propertyMetaClass(property);
                //checks if the user permitted to read a property
                if (!readPermitted(meta)) {
                    break;
                }

                View propertyView = null;
                if (view != null) {
                    ViewProperty vp = view.getProperty(property.getName());
                    if (vp != null)
                        propertyView = vp.getView();
                }

                if (!property.getRange().getCardinality().isMany()) {
                    Element referenceEl = instanceEl.addElement("reference");
                    referenceEl.addAttribute("name", property.getName());
                    encodeEntity((Entity) value, visited, propertyView, referenceEl);
                } else {
                    Element collectionEl = instanceEl.addElement("collection");
                    collectionEl.addAttribute("name", property.getName());

                    if (value == null) {
                        encodeNull(collectionEl);
                        break;
                    }

                    for (Object childEntity : (Collection) value) {
                        encodeEntity((Entity) childEntity, visited, propertyView, collectionEl);
                    }
                }
                break;
            default:
                throw new IllegalStateException("Unknown property type");
            }
        }
    }

    protected boolean attrViewPermitted(MetaClass metaClass, String property) {
        return attrPermitted(metaClass, property, EntityAttrAccess.VIEW);
    }

    protected boolean attrModifyPermitted(MetaClass metaClass, String property) {
        return attrPermitted(metaClass, property, EntityAttrAccess.MODIFY);
    }

    protected boolean attrPermitted(MetaClass metaClass, String property, EntityAttrAccess entityAttrAccess) {
        Security security = AppBeans.get(Security.NAME);
        return security.isEntityAttrPermitted(metaClass, property, entityAttrAccess);
    }

    protected boolean readPermitted(MetaClass metaClass) {
        return entityOpPermitted(metaClass, EntityOp.READ);
    }

    protected boolean updatePermitted(MetaClass metaClass) {
        return entityOpPermitted(metaClass, EntityOp.UPDATE);
    }

    protected boolean entityOpPermitted(MetaClass metaClass, EntityOp entityOp) {
        Security security = AppBeans.get(Security.NAME);
        return security.isEntityOpPermitted(metaClass, entityOp);
    }

    protected MetaClass propertyMetaClass(MetaProperty property) {
        return property.getRange().asClass();
    }

    protected boolean isPropertyIncluded(View view, MetaProperty metaProperty) {
        if (view == null) {
            return true;
        }

        ViewProperty viewProperty = view.getProperty(metaProperty.getName());
        return (viewProperty != null);
    }

    protected void encodeNull(Element element) {
        element.addAttribute("null", "true");
    }

    protected String documentToString(Document document) {
        try {
            OutputFormat format = OutputFormat.createPrettyPrint();
            StringWriter sw = new StringWriter();
            XMLWriter writer = new XMLWriter(sw, format);
            writer.write(document);
            return sw.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}