/*
* Data Hub Service (DHuS) - For Space data distribution.
* Copyright (C) 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.v1.entity.AbstractEntity;
import fr.gael.dhus.olingo.v1.entityset.AbstractEntitySet;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
import org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent;
import org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent;
import org.apache.olingo.odata2.api.ep.callback.WriteCallbackContext;
import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackContext;
import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackResult;
import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackContext;
import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackResult;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.exception.ODataMessageException;
/**
* This class handles $expand requests, it deleguates to the Entity or the EntitySet if the
* navigation link to be expanded is an Entity (*_TO_ONE) or an EntitySet (*_TO_MANY).
* <p>
* To enable support for the `$expand` parameter, please override the following methods:
* <ul>
* <li>{@link AbstractEntity#getExpandableNavLinkNames()}
* <li>{@link AbstractEntity#expand(String, String)}
* <li>{@link AbstractEntitySet#getExpandableNavLinkNames()}
* <li>{@link AbstractEntitySet#expand(String, String, Map, Map)}
* </ul>
*/
public class Expander implements OnWriteEntryContent, OnWriteFeedContent
{
/** The root par of the URI to this OData service. */
private final URI serviceRoot;
/** Feed expended with retrieveFeedResult(). */
private final Map<?, AbstractEntity> feed;
/** EntitySet of feed to expand. */
private final AbstractEntitySet entitySet;
/** Entity expanded with retrieveEntryResult(). */
private final AbstractEntity entity;
/**
* Expand a feed.
* @param service_root the root par of the URI to this OData service.
* @param entity_set of feed to expand.
* @param feed to expand.
*/
public Expander(URI service_root, AbstractEntitySet entity_set, Map<?, AbstractEntity> feed)
{
Objects.requireNonNull(service_root);
Objects.requireNonNull(entity_set);
Objects.requireNonNull(feed);
this.serviceRoot = service_root;
this.feed = feed;
this.entity = null;
this.entitySet = entity_set;
}
/**
* Expand an entity.
* @param service_root the root par of the URI to this OData service.
* @param entity to expand.
*/
public Expander(URI service_root, AbstractEntity entity)
{
Objects.requireNonNull(service_root);
Objects.requireNonNull(entity);
this.serviceRoot = service_root;
this.feed = null;
this.entity = entity;
this.entitySet = null;
}
/**
* Deleguates to the expand() method of AbstractEntitySet or AbstractEntity.
* @param context of query to expand.
* @return data.
* @throws ODataMessageException Olingo exception occured.
*/
private List<Map<String, Object>> getData(WriteCallbackContext context)
{
try
{
String navlink_name = context.getNavigationProperty().getName();
if (this.entity != null)
{
return this.entity.expand(navlink_name, this.serviceRoot.toString());
}
else
{
Map<String, Object> key = context.extractKeyFromEntryData();
return this.entitySet.expand(navlink_name, this.serviceRoot.toString(), feed, key);
}
}
catch(ODataMessageException ex)
{
// rethrow programmatic exception
throw new RuntimeException(ex);
}
}
@Override
public WriteEntryCallbackResult retrieveEntryResult(WriteEntryCallbackContext context)
throws ODataApplicationException
{
EntityProviderWriteProperties inlineProperties = EntityProviderWriteProperties
.serviceRoot(this.serviceRoot)
.expandSelectTree(context.getCurrentExpandSelectTreeNode())
.build();
WriteEntryCallbackResult result = new WriteEntryCallbackResult();
result.setInlineProperties(inlineProperties);
List<Map<String, Object>> data = getData(context);
if (data.size() > 1)
{
throw new IllegalStateException("cannot expand a feed as an entity");
}
if (data.size() == 1)
{
result.setEntryData(data.get(0));
}
return result;
}
@Override
public WriteFeedCallbackResult retrieveFeedResult(WriteFeedCallbackContext context)
throws ODataApplicationException
{
EntityProviderWriteProperties inlineProperties = EntityProviderWriteProperties
.serviceRoot(this.serviceRoot)
.expandSelectTree(context.getCurrentExpandSelectTreeNode())
.selfLink(context.getSelfLink())
.build();
WriteFeedCallbackResult result = new WriteFeedCallbackResult();
result.setInlineProperties(inlineProperties);
result.setFeedData(getData(context));
return result;
}
/**
* Helper function to create the data for inlined feed from a Map data source.
* @param map a map<Key, Entity> expanded.
* @param self_url the absolute url to address the owning entity.
* @return the expanded feed data (non null, may be empty).
*/
public static List<Map<String, Object>> mapToData(Map<?, ? extends AbstractEntity> map, String self_url)
{
if (map.isEmpty())
{
return Collections.emptyList();
}
List<Map<String, Object>> res = new ArrayList<>(map.size());
for (AbstractEntity feed_entry: map.values())
{
res.add(feed_entry.toEntityResponse(self_url));
}
return res;
}
/**
* Helper function to create the data for inlined entity from an Entity.
* @param entity expanded.
* @param self_url the absolute url to address the owning entity.
* @return the expanded data (non null, may be empty).
*/
public static List<Map<String, Object>> entityToData(AbstractEntity entity, String self_url)
{
if (entity == null)
{
return Collections.emptyList();
}
return Collections.singletonList(entity.toEntityResponse(self_url));
}
/**
* Helper function to delegate to the right Entity from a singleton key.
* @param navlink_name navlink_name name of the navigation link to expand.
* @param self_url the absolute url to address the owning entity.
* @param entities that are part of the response and will hold the inlined entities returned by
* this method.
* @param key that identifies the Entity from the feed being served to the client.
* @param keykey key to get the single key from the `key` map.
* @return the expanded data (non null, may be empty).
*/
public static List<Map<String, Object>> expandFeedSingletonKey(String navlink_name,
String self_url, Map<?, AbstractEntity> entities, Map<String, Object> key, String keykey)
{
if (entities == null || entities.isEmpty())
{
return Collections.emptyList();
}
Object keypo = key.get(keykey);
return entities.get(keypo).expand(navlink_name, self_url);
}
}