fr.gael.dhus.olingo.v1.V1Processor.java Source code

Java tutorial

Introduction

Here is the source code for fr.gael.dhus.olingo.v1.V1Processor.java

Source

/*
 * Data Hub Service (DHuS) - For Space data distribution.
 * Copyright (C) 2013,2014,2015 GAEL Systems
 *
 * This file is part of DHuS software sources.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package fr.gael.dhus.olingo.v1;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import fr.gael.dhus.database.object.Role;
import fr.gael.dhus.service.SecurityService;
import org.apache.http.client.utils.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.api.commons.InlineCount;
import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.edm.EdmEntitySet;
import org.apache.olingo.odata2.api.edm.EdmEntityType;
import org.apache.olingo.odata2.api.edm.EdmProperty;
import org.apache.olingo.odata2.api.ep.EntityProvider;
import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties;
import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties.ODataEntityProviderPropertiesBuilder;
import org.apache.olingo.odata2.api.ep.entry.ODataEntry;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataProcessor;
import org.apache.olingo.odata2.api.processor.ODataResponse;
import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
import org.apache.olingo.odata2.api.uri.KeyPredicate;
import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
import org.apache.olingo.odata2.api.uri.info.DeleteUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetComplexPropertyUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetCountUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetLinksUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntitySetUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetEntityUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetMediaResourceUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetServiceDocumentUriInfo;
import org.apache.olingo.odata2.api.uri.info.GetSimplePropertyUriInfo;
import org.apache.olingo.odata2.api.uri.info.PostUriInfo;
import org.apache.olingo.odata2.api.uri.info.PutMergePatchUriInfo;
import org.apache.olingo.odata2.core.uri.ExpandSelectTreeCreator;

import org.w3c.dom.Document;

import fr.gael.dhus.olingo.v1.entity.Product;
import fr.gael.dhus.olingo.v1.entity.Synchronizer;
import fr.gael.dhus.olingo.v1.entity.User;
import fr.gael.dhus.olingo.v1.entity.UserSynchronizer;
import fr.gael.dhus.olingo.v1.entity.V1Entity;
import fr.gael.dhus.olingo.v1.map.SubMap;
import fr.gael.dhus.olingo.v1.map.SubMapBuilder;
import fr.gael.dhus.spring.context.ApplicationContextProvider;
import fr.gael.dhus.system.config.ConfigurationManager;
import fr.gael.dhus.util.MetalinkBuilder;

/**
 * Processes every resources request. Executes the CRUD commands. Each method is
 * prefixed by 'create', 'read', 'update' or 'delete'. URLs are validated by the
 * UriParser.
 */
public class V1Processor extends ODataSingleProcessor {
    private static Logger logger = LogManager.getLogger();
    private static ConfigurationManager configurationManager = ApplicationContextProvider
            .getBean(ConfigurationManager.class);

    /**
     * This OData service allows the metalink content type for the EntitySet
     * Products.
     */
    @Override
    public List<String> getCustomContentTypes(Class<? extends ODataProcessor> processor_feature)
            throws ODataException {
        return Collections.singletonList(MetalinkBuilder.CONTENT_TYPE);
    }

    @Override
    public ODataResponse readServiceDocument(GetServiceDocumentUriInfo uri_info, String content_type)
            throws ODataException {
        Edm edm = new V1EdmWrapper(getContext().getService().getEntityDataModel());
        return EntityProvider.writeServiceDocument(content_type, edm, getServiceRoot().toASCIIString());
    }

    /** Writes an EntitySet eg: http://dhus.gael.fr/odata/v1/Collections */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public ODataResponse readEntitySet(GetEntitySetUriInfo uri_info, String content_type) throws ODataException {
        int maxrows = configurationManager.getOdataConfiguration().getMaxRows();

        List<Map<String, Object>> building = new ArrayList<Map<String, Object>>();
        boolean doPagination = false;

        // Gets values for `skip` and `top` (pagination).
        int skip = (uri_info.getSkip() == null) ? 0 : uri_info.getSkip();
        int top = (uri_info.getTop() == null) ? maxrows : uri_info.getTop();

        // Gets the `collection` part of the URI.
        EdmEntitySet targetES = uri_info.getTargetEntitySet();
        KeyPredicate startKP = (uri_info.getKeyPredicates().size() == 0) ? null
                : uri_info.getKeyPredicates().get(0);

        Navigator<Map> navigator = new Navigator<Map>(uri_info.getStartEntitySet(), startKP,
                uri_info.getNavigationSegments(), Map.class);

        // Creates the EntitySetResponseBuilder.
        if (targetES.getName().equals(V1Model.PRODUCT.getName())
                || targetES.getName().equals(V1Model.COLLECTION.getName())
                || targetES.getName().equals(V1Model.NODE.getName())
                || targetES.getName().equals(V1Model.USER.getName())
                || targetES.getName().equals(V1Model.CONNECTION.getName())) {
            doPagination = true;
        } else if (!targetES.getName().equals(V1Model.ATTRIBUTE.getName())
                && !targetES.getName().equals(V1Model.CLASS.getName())
                && !targetES.getName().equals(V1Model.SYNCHRONIZER.getName())
                && !targetES.getName().equals(V1Model.NETWORK.getName())
                && !targetES.getName().equals(V1Model.NETWORKSTATISTIC.getName())
                && !targetES.getName().equals(V1Model.RESTRICTION.getName())
                && !targetES.getName().equals(V1Model.SYSTEM_ROLE.getName())
                && !targetES.getName().equals(V1Model.USER_SYNCHRONIZER.getName())) {
            throw new ODataException("Target EntitySet not allowed.");
        }

        // Builds the response.
        Map results = navigator.navigate();
        int inlineCount = results.size();
        FilterExpression filter = uri_info.getFilter();
        OrderByExpression orderBy = uri_info.getOrderBy();

        if (uri_info.getInlineCount() != null && uri_info.getInlineCount().equals(InlineCount.ALLPAGES)
                && results instanceof SubMap && filter != null) {
            SubMapBuilder smb = ((SubMap) results).getSubMapBuilder();
            smb.setFilter(filter);
            results = smb.build();
            inlineCount = results.size();
        }

        // Skip, Sort and Filter.
        if (results instanceof SubMap && (filter != null || orderBy != null || skip != 0 || top != 0)) {
            SubMapBuilder smb = ((SubMap) results).getSubMapBuilder();
            smb.setFilter(filter).setOrderBy(orderBy);
            smb.setSkip(skip);
            smb.setTop(top);
            results = smb.build();
        }
        try {
            if (uri_info.getFormat() != null && uri_info.getFormat().equals(MetalinkBuilder.CONTENT_TYPE)
                    && targetES.getName().equals(V1Model.PRODUCT.getName())) {
                List<Product> res = new ArrayList<Product>();
                // Feeds the EntitySetResponseBuilder.
                Iterator it = results.values().iterator();
                while (it.hasNext()) {
                    Object o = it.next();
                    res.add((Product) o);
                }
                // Iterators may be scrolls on the database.
                if (it instanceof Closeable) {
                    try {
                        ((Closeable) it).close();
                    } catch (IOException e) {
                        logger.warn("Cannot close iterator:", e);
                    }
                }

                return ODataResponse
                        .fromResponse(EntityProvider.writeBinary(MetalinkBuilder.CONTENT_TYPE,
                                makeMetalinkDocument(res).getBytes("UTF-8")))
                        .header("Content-Disposition", "inline; filename=products" + MetalinkBuilder.FILE_EXTENSION)
                        .build();
            }
        } catch (UnsupportedEncodingException e) {
            throw new ODataException(e);
        }

        // Feeds the EntitySetResponseBuilder.
        Iterator<V1Entity> it = results.values().iterator();
        int i;
        for (i = 0; it.hasNext(); i++) {
            V1Entity o = it.next();
            building.add(o.toEntityResponse(makeLink().toString()));
        }
        // Iterators may be scrolls on the database.
        if (it instanceof Closeable) {
            try {
                ((Closeable) it).close();
            } catch (IOException e) {
                logger.warn("Cannot close iterator:", e);
            }
        }

        ODataEntityProviderPropertiesBuilder builder = EntityProviderWriteProperties.serviceRoot(makeLink());

        // Creates the `next` link.
        if (doPagination && i == top && it.hasNext()) {
            i += skip;
            builder.nextLink(makeNextLink(i));
        }
        String targetName = uri_info.getTargetEntitySet().getName();

        ExpandSelectTreeCreator creator = new ExpandSelectTreeCreator(uri_info.getSelect(), uri_info.getExpand());
        builder.expandSelectTree(creator.create());
        builder.callbacks(V1Model.getEntitySet(targetName).getCallbacks(makeLink(false)));

        if (uri_info.getInlineCount() != null && uri_info.getInlineCount().equals(InlineCount.ALLPAGES)) {
            builder.inlineCountType(uri_info.getInlineCount());
            builder.inlineCount(inlineCount);
        }

        return EntityProvider.writeFeed(content_type, targetES, building, builder.build());
    }

    @SuppressWarnings("rawtypes")
    @Override
    public ODataResponse countEntitySet(final GetEntitySetCountUriInfo uri_info, final String content_type)
            throws ODataException {
        // Gets the `collection` part of the URI.
        EdmEntitySet targetES = uri_info.getTargetEntitySet();
        KeyPredicate startKP = (uri_info.getKeyPredicates().size() == 0) ? null
                : uri_info.getKeyPredicates().get(0);

        Navigator<Map> navigator = new Navigator<Map>(uri_info.getStartEntitySet(), startKP,
                uri_info.getNavigationSegments(), Map.class);

        // Creates the EntitySetResponseBuilder.
        if (!targetES.getName().equals(V1Model.PRODUCT.getName())
                && !targetES.getName().equals(V1Model.COLLECTION.getName())
                && !targetES.getName().equals(V1Model.NODE.getName())
                && !targetES.getName().equals(V1Model.ATTRIBUTE.getName())
                && !targetES.getName().equals(V1Model.CLASS.getName())
                && !targetES.getName().equals(V1Model.SYNCHRONIZER.getName())
                && !targetES.getName().equals(V1Model.USER.getName())
                && !targetES.getName().equals(V1Model.CONNECTION.getName())
                && !targetES.getName().equals(V1Model.NETWORK.getName())
                && !targetES.getName().equals(V1Model.NETWORKSTATISTIC.getName())
                && !targetES.getName().equals(V1Model.RESTRICTION.getName())
                && !targetES.getName().equals(V1Model.SYSTEM_ROLE.getName())
                && !targetES.getName().equals(V1Model.USER_SYNCHRONIZER.getName())) {
            throw new ODataException("Target EntitySet not allowed.");
        }

        // Builds the response.
        Map<?, ?> results = navigator.navigate();

        FilterExpression filter = uri_info.getFilter();
        // Skip, Sort and Filter.
        if (results instanceof SubMap && (filter != null)) {
            SubMapBuilder smb = ((SubMap) results).getSubMapBuilder();
            smb.setFilter(filter);
            results = smb.build();
        }

        return ODataResponse.entity(results.size()).build();
    }

    /** Writes an Entity eg: http://dhus.gael.fr/odata/v1/Collections(10) */
    @Override
    public ODataResponse readEntity(GetEntityUriInfo uri_info, String content_type) throws ODataException {
        ODataResponse rsp = null;
        String targetName = uri_info.getTargetEntitySet().getName();
        Map<String, Object> data = V1Model.getEntitySet(targetName).getEntityResponse(uri_info,
                makeLink().toString());

        ExpandSelectTreeCreator creator = new ExpandSelectTreeCreator(uri_info.getSelect(), uri_info.getExpand());

        EntityProviderWriteProperties p = EntityProviderWriteProperties.serviceRoot(makeLink())
                .expandSelectTree(creator.create())
                .callbacks(V1Model.getEntitySet(targetName).getCallbacks(makeLink(false))).build();
        rsp = EntityProvider.writeEntry(content_type, uri_info.getTargetEntitySet(), data, p);
        return rsp;
    }

    /** Writes a Stream eg: http://dhus.gael.fr/odata/v1/Products('8')/$value */
    @Override
    public ODataResponse readEntityMedia(GetMediaResourceUriInfo uri_info, String content_type)
            throws ODataException {
        String targetName = uri_info.getTargetEntitySet().getName();
        return V1Model.getEntitySet(targetName).getEntityMedia(uri_info, this);
    }

    /**
     * Writes Links eg:
     * http://dhus.gael.fr/odata/v1/Collections(10)/$links/Products
     */
    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public ODataResponse readEntityLinks(GetEntitySetLinksUriInfo uri_info, String content_type)
            throws ODataException {
        ODataResponse rsp = null;
        List<Map<String, Object>> building = new ArrayList<Map<String, Object>>();

        // Gets the `collection` part of the URI.
        EdmEntitySet targetES = uri_info.getTargetEntitySet();
        KeyPredicate startKP = (uri_info.getKeyPredicates().size() == 0) ? null
                : uri_info.getKeyPredicates().get(0);

        boolean doPagination = false;
        if (targetES.getName().equals(V1Model.PRODUCT.getName())
                || targetES.getName().equals(V1Model.COLLECTION.getName())) {
            doPagination = true;
        } else if (!targetES.getName().equals(V1Model.NODE.getName())
                && !targetES.getName().equals(V1Model.ATTRIBUTE.getName())
                && !targetES.getName().equals(V1Model.CLASS.getName())) {
            throw new ODataException("Target EntitySet not allowed.");
        }

        Navigator<Map> navigator = new Navigator<Map>(uri_info.getStartEntitySet(), startKP,
                uri_info.getNavigationSegments(), Map.class);
        Map results = navigator.navigate();

        int maxrows = configurationManager.getOdataConfiguration().getMaxRows();

        int skip = (uri_info.getSkip() == null) ? 0 : uri_info.getSkip();
        int top = (uri_info.getTop() == null) ? maxrows : uri_info.getTop();
        FilterExpression filter = uri_info.getFilter();
        if (results instanceof SubMap && (filter != null || skip != 0 || top != 0)) {
            SubMapBuilder smb = ((SubMap) results).getSubMapBuilder();
            smb.setFilter(filter);
            smb.setSkip(skip);
            smb.setTop(top);
            results = smb.build();
        }

        // Feeds the EntitySetResponseBuilder.
        Iterator<V1Entity> it = results.values().iterator();
        int i;
        for (i = 0; it.hasNext(); i++) {
            building.add(it.next().toEntityResponse(makeLink().toString()));
        }

        if (it instanceof Closeable) {
            try {
                ((Closeable) it).close();
            } catch (IOException e) {
                logger.warn("Cannot close iterator:", e);
            }
        }

        ODataEntityProviderPropertiesBuilder builder = EntityProviderWriteProperties.serviceRoot(makeLink());

        // Creates the `next` link.
        if (doPagination && i == top && it.hasNext()) {
            i += skip;
            builder.nextLink(makeNextLink(i));
        }

        rsp = EntityProvider.writeLinks(content_type, targetES, building, builder.build());
        return rsp;
    }

    /** Writes a Property eg: http://dhus.gael.fr/odata/v1/Products('8')/Name/ */
    @Override
    public ODataResponse readEntitySimpleProperty(GetSimplePropertyUriInfo uri_info, String content_type)
            throws ODataException {
        Object value = readPropertyValue(uri_info);
        EdmProperty target = uri_info.getPropertyPath().get(uri_info.getPropertyPath().size() - 1);
        return EntityProvider.writeProperty(content_type, target, value);
    }

    /**
     * Writes a complex Property eg:
     * http://dhus.gael.fr/odata/v1/Products('8')/ContentDate/
     */
    @Override
    public ODataResponse readEntityComplexProperty(GetComplexPropertyUriInfo uri_info, String content_type)
            throws ODataException {
        EdmProperty target = uri_info.getPropertyPath().get(uri_info.getPropertyPath().size() - 1);
        String entityTarget = uri_info.getTargetEntitySet().getName();
        Map<String, Object> values = V1Model.getEntitySet(entityTarget).getComplexProperty(uri_info);
        return EntityProvider.writeProperty(content_type, target, values);
    }

    /**
     * Writes a Property eg:
     * http://dhus.gael.fr/odata/v1/Products('8')/Name/$value
     */
    @Override
    public ODataResponse readEntitySimplePropertyValue(GetSimplePropertyUriInfo uri_info, String content_type)
            throws ODataException {
        try {
            Object value = readPropertyValue(uri_info);
            EdmProperty target = uri_info.getPropertyPath().get(uri_info.getPropertyPath().size() - 1);

            if (target.getName().equals("Metalink")) // Metalink/$value
            {
                return ODataResponse
                        .fromResponse(EntityProvider.writeBinary(MetalinkBuilder.CONTENT_TYPE,
                                value.toString().getBytes("UTF-8")))
                        .header("Content-Disposition", "inline; filename=product" + MetalinkBuilder.FILE_EXTENSION)
                        .build();
            } else {
                return EntityProvider.writePropertyValue(target, value);
            }
        } catch (UnsupportedEncodingException e) {
            throw new ODataException(e);
        }
    }

    @Override
    public ODataResponse createEntity(PostUriInfo uri_info, InputStream content, String rq_content_type,
            String content_type) throws ODataException {
        if (uri_info.getNavigationSegments().size() > 0) {
            throw new ODataException("No support for linking a new entry");
        }

        if (uri_info.getStartEntitySet().getEntityType().hasStream()) {
            throw new ODataException("No support for media resources");
        }

        // Merge semantics is set to FALSE because this is `create` (POST)
        EntityProviderReadProperties properties = EntityProviderReadProperties.init().mergeSemantic(false).build();
        ODataEntry entry = EntityProvider.readEntry(rq_content_type, uri_info.getStartEntitySet(), content,
                properties);

        EdmEntityType target_et = uri_info.getTargetEntitySet().getEntityType();

        Map<String, Object> res = null;
        fr.gael.dhus.database.object.User current_user = ApplicationContextProvider.getBean(SecurityService.class)
                .getCurrentUser();
        if (target_et.getName().equals(V1Model.SYNCHRONIZER.getEntityName())) {
            if (V1Model.SYNCHRONIZER.isAuthorized(current_user)) {
                Synchronizer sync = new Synchronizer(entry);
                res = sync.toEntityResponse(makeLink().toString());
            } else {
                throw new ODataException("You are authorized to create a product synchronizer");
            }
        } else if (target_et.getName().equals(V1Model.USER_SYNCHRONIZER.getEntityName())) {
            if (V1Model.USER_SYNCHRONIZER.isAuthorized(current_user)) {
                UserSynchronizer sync = new UserSynchronizer(entry);
                res = sync.toEntityResponse(makeLink().toString());
            } else {
                throw new ODataException("You are authorized to create a user synchronizer");
            }
        } else {
            throw new ODataException("Given EntitySet is not writable");
        }

        return EntityProvider.writeEntry(content_type, uri_info.getStartEntitySet(), res,
                EntityProviderWriteProperties.serviceRoot(getContext().getPathInfo().getServiceRoot()).build());
    }

    @Override
    public ODataResponse updateEntity(PutMergePatchUriInfo uri_info, InputStream content, String rq_content_type,
            boolean merge, String content_type) throws ODataException {
        EntityProviderReadProperties properties = EntityProviderReadProperties.init().mergeSemantic(merge).build();
        ODataEntry entry = EntityProvider.readEntry(rq_content_type, uri_info.getStartEntitySet(), content,
                properties);

        fr.gael.dhus.database.object.User current_user = ApplicationContextProvider.getBean(SecurityService.class)
                .getCurrentUser();

        EdmEntityType target_et = uri_info.getTargetEntitySet().getEntityType();
        try {
            String target_entity = target_et.getName();
            if (target_entity.equals(V1Model.SYNCHRONIZER.getEntityName())) {
                if (V1Model.SYNCHRONIZER.isAuthorized(current_user)) {
                    long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
                    Synchronizer s = new Synchronizer(key);
                    s.updateFromEntry(entry);
                } else {
                    throw new ODataException("You are not authorized to update a product synchronizer");
                }
            } else if (target_entity.equals(V1Model.USER.getEntityName())) {
                String key = uri_info.getKeyPredicates().get(0).getLiteral();
                User u = new User(key);
                if (u.isAuthorize(current_user)) {
                    u.updateFromEntry(entry);
                } else {
                    throw new ODataException("You are not authorized to update this user");
                }
            } else if (target_entity.equals(V1Model.USER_SYNCHRONIZER.getEntityName())) {
                if (V1Model.USER_SYNCHRONIZER.isAuthorized(current_user)) {
                    long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
                    UserSynchronizer s = new UserSynchronizer(key);
                    s.updateFromEntry(entry);
                } else {
                    throw new ODataException("You are not authorized to update a user synchronizer");
                }
            } else {
                throw new ODataException("Given EntitySet is not writable");
            }
        } catch (NullPointerException e) {
            return ODataResponse.status(HttpStatusCodes.NOT_FOUND).build();
        }

        return ODataResponse.status(HttpStatusCodes.NO_CONTENT).build();
    }

    @Override
    public ODataResponse deleteEntity(DeleteUriInfo uri_info, String content_type) throws ODataException {
        fr.gael.dhus.database.object.User current_user = ApplicationContextProvider.getBean(SecurityService.class)
                .getCurrentUser();

        EdmEntityType target_et = uri_info.getTargetEntitySet().getEntityType();
        String target_name = target_et.getName();
        if (target_name.equals(V1Model.SYNCHRONIZER.getEntityName())) {
            if (V1Model.SYNCHRONIZER.isAuthorized(current_user)) {
                long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
                Synchronizer.delete(key);
            } else {
                throw new ODataException("You are not authorized to delete a product synchronizer");
            }
        } else if (target_name.equals(V1Model.USER_SYNCHRONIZER.getEntityName())) {
            if (V1Model.USER_SYNCHRONIZER.isAuthorized(current_user)) {
                long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
                Synchronizer.delete(key); // using the same method to delete
            } else {
                throw new ODataException("You are not authorized to delete a user synchronizer");
            }
        } else {
            throw new ODataException("Given EntitySet is not writable");
        }

        return ODataResponse.status(HttpStatusCodes.NO_CONTENT).build();
    }

    /** Returns the value of the given Property. */
    private Object readPropertyValue(GetSimplePropertyUriInfo uri_info) throws ODataException {
        String targetESName = uri_info.getTargetEntitySet().getName();
        EdmProperty target = uri_info.getPropertyPath().get(uri_info.getPropertyPath().size() - 1);
        String propName = target.getName();
        // Particular case of Metalink
        if (targetESName.equals(V1Model.PRODUCT.getName()) && propName.equals("Metalink")) {
            KeyPredicate startKP = uri_info.getKeyPredicates().get(0);

            Navigator<Product> navigator = new Navigator<Product>(uri_info.getStartEntitySet(), startKP,
                    uri_info.getNavigationSegments(), Product.class);
            Product p = navigator.navigate();

            return makeMetalinkDocument(Collections.singletonList(p));
        }
        return V1Model.getEntitySet(targetESName).readPropertyValue(uri_info);
    }

    /** Makes the metalink XML Document for a given list of products. */
    private String makeMetalinkDocument(Iterable<Product> lp) throws ODataException {
        try {
            MetalinkBuilder mb = new MetalinkBuilder();

            for (Product p : lp) {
                String serviceRoot = makeLink().toString();
                String product_entity = "";
                if (!serviceRoot.contains(V1Model.PRODUCT.getName()))
                    product_entity = V1Model.PRODUCT.getName() + "('" + p.getId() + "')/";

                String url = serviceRoot + product_entity + "$value";
                mb.addFile(p.getName() + ".zip").addUrl(url, null, 0);
            }
            StringWriter sw = new StringWriter();

            Document doc = mb.build();
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            transformer.transform(new DOMSource(doc), new StreamResult(sw));
            return sw.toString();
        } catch (ParserConfigurationException e) {
            throw new ODataException(e);
        } catch (TransformerException e) {
            throw new ODataException(e);
        }
    }

    /** Makes the `next` link for navigation purposes. */
    private String makeNextLink(int skip) throws ODataException {
        URI selfLnk = getServiceRoot();
        URIBuilder ub = new URIBuilder(selfLnk);
        ub.setParameter("$skip", String.valueOf(skip));
        return ub.toString();
    }

    private URI makeLink() throws ODataException {
        return makeLink(true);
    }

    private URI makeLink(boolean remove_last_segment) throws ODataException {
        URI selfLnk = getServiceRoot();
        StringBuilder sb = new StringBuilder(selfLnk.getPath());

        if (remove_last_segment) {
            // Removes the last segment.
            int lio = sb.lastIndexOf("/");
            while (lio != -1 && lio == sb.length() - 1) {
                sb.deleteCharAt(lio);
                lio = sb.lastIndexOf("/");
            }
            if (lio != -1)
                sb.delete(lio + 1, sb.length());

            // Removes the `$links` segment.
            lio = sb.lastIndexOf("$links/");
            if (lio != -1)
                sb.delete(lio, lio + 7);
        } else {
            if (!sb.toString().endsWith("/") && !sb.toString().endsWith("\\")) {
                sb.append("/");
            }
        }
        try {
            URI res = new URI(selfLnk.getScheme(), selfLnk.getUserInfo(), selfLnk.getHost(), selfLnk.getPort(),
                    sb.toString(), null, selfLnk.getFragment());
            return res;
        } catch (Exception e) {
            throw new ODataException(e);
        }
    }

    /**
     * Originally, service root is extracted from http header with URI
     * serviceRoot = getContext ().getPathInfo ().getServiceRoot (); In case of
     * proxied http service without "Preserve" settings local uri is returned.
     * Dhus allow the user to configure the url from which the system can be
     * visible on the internet.
     * 
     * @return the base URI for service root
     * @throws ODataException if URI is wrong
     */
    private URI getServiceRoot() throws ODataException {
        String odata_header_url = getContext().getPathInfo().getRequestUri().toString();
        String path = odata_header_url.substring(odata_header_url.indexOf(V1Util.getBasePath()));

        String url = ApplicationContextProvider.getBean(ConfigurationManager.class).getServerConfiguration()
                .getExternalUrl();
        try {
            return new URI(url + path);
        } catch (URISyntaxException e) {
            throw new ODataException("Cannot compute serive root URI.", e);
        }
    }
}