/*
* Data Hub Service (DHuS) - For Space data distribution.
* Copyright (C) 2013,2014,2015,2016 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 fr.gael.dhus.olingo.Security;
import fr.gael.dhus.olingo.v1.ExpectedException.InvalidKeyException;
import fr.gael.dhus.olingo.v1.ExpectedException.NotAllowedException;
import fr.gael.dhus.olingo.v1.ExpectedException.NotImplementedException;
import fr.gael.dhus.olingo.v1.entity.Ingest;
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.AbstractEntity;
import fr.gael.dhus.olingo.v1.entityset.AbstractEntitySet;
import fr.gael.dhus.olingo.v1.map.SubMap;
import fr.gael.dhus.olingo.v1.map.SubMapBuilder;
import fr.gael.dhus.olingo.v1.operations.AbstractOperation;
import fr.gael.dhus.service.SecurityService;
import fr.gael.dhus.spring.context.ApplicationContextProvider;
import fr.gael.dhus.system.config.ConfigurationManager;
import fr.gael.dhus.util.MetalinkBuilder;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.ODataCallback;
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.EdmFunctionImport;
import org.apache.olingo.odata2.api.edm.EdmLiteral;
import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
import org.apache.olingo.odata2.api.edm.EdmProperty;
import org.apache.olingo.odata2.api.edm.EdmSimpleType;
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.rt.RuntimeDelegate;
import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
import org.apache.olingo.odata2.api.uri.KeyPredicate;
import org.apache.olingo.odata2.api.uri.NavigationSegment;
import org.apache.olingo.odata2.api.uri.PathSegment;
import org.apache.olingo.odata2.api.uri.UriInfo;
import org.apache.olingo.odata2.api.uri.UriParser;
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.GetFunctionImportUriInfo;
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;
/**
* 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 Processor extends ODataSingleProcessor
{
/** Extract the OData resource path from an URL. */
private static final Pattern RESOURCE_PATH_EXTRACTOR = Pattern.compile("odata/v1(/.*)$");
private static final Logger LOGGER = LogManager.getLogger(Processor.class);
private static final ConfigurationManager CONFIGURATION_MANAGER =
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 EdmWrapper(getContext().getService().getEntityDataModel());
return EntityProvider.writeServiceDocument(content_type, edm, ServiceFactory.ROOT_URL);
}
/* 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
{
// Gets values for `skip` and `top` (pagination).
int maxrows = CONFIGURATION_MANAGER.getOdataConfiguration().getMaxRows();
boolean doPagination = false;
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();
AbstractEntitySet target = Model.getEntitySet(targetES.getName());
boolean is_navlink = !uri_info.getNavigationSegments().isEmpty();
// Validity and security checks.
if (!target.isAuthorized(Security.getCurrentUser()) || !is_navlink && !target.isTopLevel())
{
throw new NotAllowedException();
}
// Contained target workaround (non OData2: non standard!)
if (is_navlink)
{
int last_id = getContext().getPathInfo().getODataSegments().size() - 1;
String navlinkname = getContext().getPathInfo().getODataSegments().get(last_id).getPath();
if (!navlinkname.equals(targetES.getName()))
{
targetES = new ContainedEntitySetDecorator(navlinkname, targetES);
}
}
// Enables pagination.
if (target.hasManyEntries())
{
doPagination = target.hasManyEntries();
}
// Builds the response.
KeyPredicate startKP =
(uri_info.getKeyPredicates().isEmpty()) ? null : uri_info.getKeyPredicates().get(0);
Map results = Navigator.<Map>navigate(uri_info.getStartEntitySet(), startKP,
uri_info.getNavigationSegments(), Map.class);
//int inlineCount = results.size();
int inlineCount = -1;
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();
}
// Custom format (eg: metalink)
if (uri_info.getFormat() != null)
{
if (uri_info.getFormat().equals(MetalinkBuilder.CONTENT_TYPE))
{
ArrayList<AbstractEntity> aslist = new ArrayList<>();
Iterator<AbstractEntity> it = results.values().iterator();
while (it.hasNext())
{
aslist.add(it.next());
}
return MetalinkFormatter.writeFeed(targetES, aslist, makeLink().toString());
}
}
// Feeds the EntitySetResponseBuilder.
List<Map<String, Object>> building = new ArrayList<>();
Iterator<AbstractEntity> it = results.values().iterator();
int i;
for (i = 0; it.hasNext(); i++)
{
AbstractEntity o = it.next();
building.add(o.toEntityResponse(makeLink().toString()));
if ((!it.hasNext ()) && (o instanceof Closeable))
{
try
{
Closeable.class.cast (o).close ();
}
catch (IOException e)
{
LOGGER.warn ("Cannot close resource: " + 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);
}
}
ODataEntityProviderPropertiesBuilder builder
= EntityProviderWriteProperties.serviceRoot(makeLink());
// Creates the `next` link.
if (doPagination && i == top && it.hasNext())
{
i += skip;
builder.nextLink(makeNextLink(i));
}
// $expand.
ExpandSelectTreeNode expand_select_tree
= UriParser.createExpandSelectTree(uri_info.getSelect(), uri_info.getExpand());
builder.expandSelectTree(expand_select_tree)
.callbacks(makeCallbacks(
target.getExpandableNavLinkNames(),
new Expander(makeLink(false), target, results)));
// inlinecount.
if (uri_info.getInlineCount() != null &&
uri_info.getInlineCount().equals(InlineCount.ALLPAGES))
{
if (inlineCount == -1) inlineCount=results.size();
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();
AbstractEntitySet entityset = Model.getEntitySet(targetES.getName());
// Validity and security checks.
if (!entityset.isAuthorized(Security.getCurrentUser()) ||
uri_info.getNavigationSegments().isEmpty() && !entityset.isTopLevel())
{
throw new NotAllowedException();
}
// Builds the response.
KeyPredicate startKP =
(uri_info.getKeyPredicates().isEmpty()) ? null : uri_info.getKeyPredicates().get(0);
Map<?, ?> results = Navigator.<Map>navigate(uri_info.getStartEntitySet(), startKP,
uri_info.getNavigationSegments(), Map.class);
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
{
EdmEntitySet targetES = uri_info.getTargetEntitySet();
AbstractEntitySet target = Model.getEntitySet(targetES.getName());
// Validity and security checks.
if (!target.isAuthorized(Security.getCurrentUser()))
{
throw new NotAllowedException();
}
// Contained target workaround (non OData2: non standard!)
int last_id = getContext().getPathInfo().getODataSegments().size() - 1;
String navlinkname = getContext().getPathInfo().getODataSegments().get(last_id).getPath();
// remove the key
last_id = navlinkname.indexOf('(');
if (last_id != -1)
{
navlinkname = navlinkname.substring(0, last_id);
}
if (!navlinkname.equals(targetES.getName()))
{
targetES = new ContainedEntitySetDecorator(navlinkname, targetES);
}
// Navigate to target Entity
KeyPredicate startKP = uri_info.getKeyPredicates().get(0);
AbstractEntity entity = Navigator.<AbstractEntity>navigate(
uri_info.getStartEntitySet(), startKP, uri_info.getNavigationSegments(), null);
Map<String, Object> data = entity.toEntityResponse(makeLink().toString());
// $expand & $select
ExpandSelectTreeNode expand_select_tree =
UriParser.createExpandSelectTree(uri_info.getSelect(), uri_info.getExpand());
EntityProviderWriteProperties prop = EntityProviderWriteProperties
.serviceRoot(makeLink())
.expandSelectTree(expand_select_tree)
.callbacks(makeCallbacks(
entity.getExpandableNavLinkNames(),
new Expander(makeLink(false), entity)))
.build();
return EntityProvider.writeEntry(content_type, targetES, data, prop);
}
/* 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 Model.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
{
// Target of the link to create
EdmEntitySet link_target_es = uri_info.getTargetEntitySet();
// Gets the entityset containing the navigation link to `link_target_es`
//EdmEntitySet target_es = getLinkFromES(new AdaptableUriInfo(uri_info));
//EdmEntityType target_et = target_es.getEntityType();
// Gets the `collection` part of the URI.
KeyPredicate start_kp =
(uri_info.getKeyPredicates().isEmpty()) ? null : uri_info.getKeyPredicates().get(0);
boolean do_pagination = false;
// force pagination on products and when $skip and/or $top are provided
if (link_target_es.getName().equals(Model.PRODUCT.getName()) ||
uri_info.getSkip() != null || uri_info.getTop()!= null)
{
do_pagination = true;
}
Map results = Navigator.<Map>navigate(uri_info.getStartEntitySet(), start_kp,
uri_info.getNavigationSegments(), Map.class);
if (!(results instanceof SubMap))
{
do_pagination = false;
}
int maxrows = CONFIGURATION_MANAGER.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 (do_pagination && (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.
List<Map<String, Object>> building = new ArrayList<>();
Iterator<AbstractEntity> it = results.values().iterator();
int i;
for (i = 0; it.hasNext() && (!do_pagination || i<top) ; i++)
{
building.add(it.next().toEntityResponse(makeLink().toString()));
}
ODataEntityProviderPropertiesBuilder builder
= EntityProviderWriteProperties.serviceRoot(makeLink());
// Creates the `next` link.
if (do_pagination && i == top && it.hasNext())
{
i += skip;
builder.nextLink(makeNextLink(i));
}
if (it instanceof Closeable)
{
try
{
((Closeable) it).close();
}
catch (IOException e)
{
LOGGER.warn("Cannot close iterator:", e);
}
}
return EntityProvider.writeLinks(content_type, link_target_es, building, builder.build());
}
/* 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 = Model.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 ExpectedException(e.getMessage());
}
}
@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");
}
Map<String, Object> res = null;
EdmEntityType target_et = uri_info.getTargetEntitySet().getEntityType();
fr.gael.dhus.database.object.User current_user = Security.getCurrentUser();
if (uri_info.getStartEntitySet().getEntityType().hasStream())
{
// When creating Media Entity, `content` contains the data, the OData document
// needs to be sent with an update (PUT) command later.
// See [MS-ODATA].pdf chapt. 2.2.7.1.3 (p197).
if (target_et.getName().equals(Model.INGEST.getEntityName()))
{
Ingest ingest = new Ingest(content);
res = ingest.toEntityResponse(makeLink().toString());
}
else
{
throw new NotImplementedException();
}
}
else
{
// 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);
if (target_et.getName().equals(Model.SYNCHRONIZER.getEntityName()))
{
if (Model.SYNCHRONIZER.isAuthorized(current_user))
{
Synchronizer sync = new Synchronizer(entry);
res = sync.toEntityResponse(makeLink().toString());
}
else
{
throw new NotAllowedException();
}
}
else if (target_et.getName().equals(Model.USER_SYNCHRONIZER.getEntityName()))
{
if (Model.USER_SYNCHRONIZER.isAuthorized(current_user))
{
UserSynchronizer sync = new UserSynchronizer(entry);
res = sync.toEntityResponse(makeLink().toString());
}
else
{
throw new NotAllowedException();
}
}
else
{
throw new NotImplementedException();
}
}
return EntityProvider.writeEntry(content_type,
uri_info.getStartEntitySet(), res,
EntityProviderWriteProperties
.serviceRoot(getContext().getPathInfo().getServiceRoot())
.build()
);
}
/* Creates an EntityLink, eg: http://dhus.gael.fr/odata/v1/Collections(10)/$links/Products */
@Override
public ODataResponse createEntityLink(PostUriInfo uri_info, InputStream content,
String request_content_type, String content_type) throws ODataException
{
// Target of the link to create
//EdmEntitySet link_target_es = uri_info.getTargetEntitySet();
// Gets the entityset containing the navigation link to `link_target_es`
EdmEntitySet target_es = getLinkFromES(new AdaptableUriInfo(uri_info));
EdmEntityType target_et = target_es.getEntityType();
// Check abilities and permissions
if (!target_es.getName().equals(Model.USER.getName()))
{
throw new ODataException("EntitySet " + target_et.getName() + " cannot create links");
}
fr.gael.dhus.database.object.User current_user = Security.getCurrentUser();
AbstractEntitySet es = Model.getEntitySet(target_es.getName());
if (!es.isAuthorized(current_user))
{
throw new NotAllowedException();
}
// Gets the affected entity
String key = uri_info.getKeyPredicates().get(0).getLiteral();
User user = new User(key);
// Reads and parses the link
String link = EntityProvider.readLink(content_type, target_es, content);
link = link.trim(); // Olingo does not trim... resulting in a parse exception
try
{
link = (new URI(link)).getPath();
if (link == null || link.isEmpty())
{
throw new ExpectedException("Invalid link, path is empty");
}
// Gets the OData resource path
Matcher matcher = RESOURCE_PATH_EXTRACTOR.matcher(link);
if (matcher.find())
{
link = matcher.group(1);
}
else
{
throw new ExpectedException("Invalid link, path is malformed");
}
}
catch (URISyntaxException e)
{
throw new ExpectedException(e.getMessage());
}
// Use Olingo's UriParser
UriParser urip = RuntimeDelegate.getUriParser(getContext().getService().getEntityDataModel());
List<PathSegment> path_segments = new ArrayList<>();
StringTokenizer st = new StringTokenizer(link, "/");
while (st.hasMoreTokens())
{
path_segments.add(UriParser.createPathSegment(st.nextToken(), null));
}
@SuppressWarnings("unchecked")
UriInfo uilink = urip.parse(path_segments, Collections.EMPTY_MAP);
// Creates link
user.createLink(uilink);
// Empty answer with HTTP code 204: no content
return ODataResponse.newBuilder().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 = Security.getCurrentUser();
EdmEntityType target_et = uri_info.getTargetEntitySet().getEntityType();
try
{
String target_entity = target_et.getName();
if (target_entity.equals(Model.SYNCHRONIZER.getEntityName()))
{
if (Model.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 NotAllowedException();
}
}
else if (target_entity.equals(Model.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 NotAllowedException();
}
}
else if (target_entity.equals(Model.INGEST.getEntityName()))
{
long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
Ingest i = Ingest.get(key);
if (i == null)
{
throw new InvalidKeyException(String.valueOf(key), target_entity);
}
i.updateFromEntry(entry);
}
else if (target_entity.equals(Model.USER_SYNCHRONIZER.getEntityName()))
{
if (Model.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 NotAllowedException();
}
}
else
{
throw new NotImplementedException();
}
}
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 = Security.getCurrentUser();
EdmEntityType target_et = uri_info.getTargetEntitySet().getEntityType();
String target_name = target_et.getName();
if (target_name.equals(Model.SYNCHRONIZER.getEntityName()))
{
if (Model.SYNCHRONIZER.isAuthorized(current_user))
{
long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
Synchronizer.delete(key);
}
else
{
throw new NotAllowedException();
}
}
else if (target_name.equals(Model.USER_SYNCHRONIZER.getEntityName()))
{
if (Model.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 NotAllowedException();
}
}
else if (target_et.getName().equals(Model.PRODUCT.getEntityName()))
{
String uuid = uri_info.getKeyPredicates().get(0).getLiteral();
Product.delete(uuid);
}
else if (target_et.getName().equals(Model.INGEST.getEntityName()))
{
long key = Long.decode(uri_info.getKeyPredicates().get(0).getLiteral());
Ingest.delete(key);
}
else
{
throw new NotImplementedException();
}
return ODataResponse.status(HttpStatusCodes.NO_CONTENT).build();
}
@Override
public ODataResponse deleteEntityLink(DeleteUriInfo uri_info, String content_type)
throws ODataException
{
// uriInfo#getNavigationSegments() does not return a shallow copy.
List<NavigationSegment> lns = new ArrayList<>(uri_info.getNavigationSegments().size());
lns.addAll(uri_info.getNavigationSegments());
// Removes the target for navigation purposes.
if (!lns.isEmpty())
{
lns.remove(lns.size()-1);
}
AbstractEntity entity = Navigator.<AbstractEntity>navigate(uri_info.getStartEntitySet(),
uri_info.getKeyPredicates().get(0), lns, AbstractEntity.class);
// Deletes.
entity.deleteLink(uri_info);
return ODataResponse.newBuilder().build();
}
@Override
public ODataResponse executeFunctionImport(GetFunctionImportUriInfo uri_info, String content_type)
throws ODataException
{
EdmFunctionImport function_import = uri_info.getFunctionImport();
Map<String, EdmLiteral> params = uri_info.getFunctionImportParameters();
EntityProviderWriteProperties entry_props =
EntityProviderWriteProperties.serviceRoot(makeLink()).build();
AbstractOperation op = Model.getServiceOperation(function_import.getName());
fr.gael.dhus.database.object.User current_user =
ApplicationContextProvider.getBean(SecurityService.class).getCurrentUser();
if (!op.canExecute(current_user))
{
throw new NotAllowedException();
}
Object res = op.execute(params);
return EntityProvider.writeFunctionImport(content_type, function_import, res, entry_props);
}
@Override
public ODataResponse executeFunctionImportValue(GetFunctionImportUriInfo uri_info, String content_type)
throws ODataException
{
EdmFunctionImport function_import = uri_info.getFunctionImport();
Map<String, EdmLiteral> params = uri_info.getFunctionImportParameters();
// FIXME: returned type might not be a simple type ...
EdmSimpleType type = (EdmSimpleType) function_import.getReturnType().getType();
AbstractOperation op = Model.getServiceOperation(function_import.getName());
fr.gael.dhus.database.object.User current_user =
ApplicationContextProvider.getBean(SecurityService.class).getCurrentUser();
if (!op.canExecute(current_user))
{
throw new NotAllowedException();
}
Object res = op.execute(params);
/* To handle binary results (NYI):
if (type == EdmSimpleTypeKind.Binary.getEdmSimpleTypeInstance()) {
response = EntityProvider.writeBinary(
((BinaryData) data).getMimeType(), // BinaryData is an meta-object holding the data
((BinaryData) data).getData() // and its mime type
);
}//*/
final String value = type.valueToString(res, EdmLiteralKind.DEFAULT, null);
return EntityProvider.writeText(value == null ? "" : value);
}
/**
* Extract the 'From' ES of an EntityLink.
* @param uri_info path to resource, eg: /Collections(10)/$links/Products
* @returns the EntitySet of the nav segment before the "$link" segment.
*/
private EdmEntitySet getLinkFromES(UriInfo uri_info)
{
EdmEntitySet res;
/* `uri_info`:
* StartEntitySet/Foo/bar/baz/$links/TargetEntitySet
* \__________/ \______________/
* Navigation Segments */
List<NavigationSegment> navsegs = uri_info.getNavigationSegments();
if (navsegs.size() >= 2) // `navsegs` contains at least the target segment
{
res = navsegs.get(navsegs.size()-1).getEntitySet();
}
else
{
res = uri_info.getStartEntitySet();
}
return res;
}
/** Returns the value of the given Property. */
private Object readPropertyValue(GetSimplePropertyUriInfo uri_info) throws ODataException
{
String targetESName = uri_info.getTargetEntitySet().getName();
return Model.getEntitySet(targetESName).readPropertyValue(uri_info);
}
/** Makes the `next` link for navigation purposes. */
private String makeNextLink(int skip) throws ODataException
{
try
{
String selfLnk = ServiceFactory.ROOT_URL;
URIBuilder ub = new URIBuilder(selfLnk);
ub.setParameter("$skip", String.valueOf(skip));
return ub.toString();
}
catch (URISyntaxException ex)
{
throw new ODataException("Cannot make next link", ex);
}
}
private URI makeLink() throws ODataException
{
return makeLink(true);
}
private URI makeLink(boolean remove_last_segment) throws ODataException
{
try
{
URIBuilder ub = new URIBuilder(ServiceFactory.EXTERNAL_URL);
StringBuilder sb = new StringBuilder();
String prefix = ub.getPath();
String path = getContext().getPathInfo().getRequestUri().getPath();
if (path == null || path.isEmpty() ||
prefix != null && !prefix.isEmpty() && !path.startsWith(ub.getPath()))
{
sb.append(prefix);
if (path != null)
{
if (prefix.endsWith("/") && path.startsWith("/"))
{
sb.deleteCharAt(sb.length() - 1);
}
if (!prefix.endsWith("/") && !path.startsWith("/"))
{
sb.append('/');
}
}
}
sb.append(path);
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("/");
}
ub.setPath(sb.toString());
return ub.build();
}
catch (NullPointerException | URISyntaxException e)
{
throw new ODataException(e);
}
}
private Map<String, ODataCallback> makeCallbacks(List<String> expandables, Expander expander)
{
Objects.requireNonNull(expandables);
Objects.requireNonNull(expander);
if (expandables.isEmpty())
{
return Collections.emptyMap();
}
Map<String, ODataCallback> res = new HashMap<>(expandables.size());
for (String navlink_name: expandables)
{
res.put(navlink_name, expander);
}
return res;
}
}