java - 带有投影的JAVA中的OData REST API

标签 java .net rest odata projection

可以在JAVA中做类似的事情吗?如果是,怎么办? (附样品)

考虑使用http://olingo.apache.org/或odata4j

我在.NET中制作了一个示例,该示例公开了OData格式的REST API(使我的消费者可以使用标准化协议进行过滤,选择,排序等)。

注意事项:我公开了不同于ORM的模型(避免公开数据库,并提供控制/限制使用者查询的可能性)。
两个用户类:Models.User和DAL.User

样品:

public IHttpActionResult GetUsers(ODataQueryOptions<Models.User> queryOptions)
{
    //Get the IQueryable from DB/Repository
    DAL.UsersDAO usersDAO = new DAL.UsersDAO();
    IQueryable<DAL.User> usersQueryable = usersDAO.GetUsers();

    //Make the projection from the 'User of Project DAL' to the 'User of ODataProject'
    IQueryable<Models.User> usersProjected = usersQueryable.Select(user => new Models.User()
    {
        Id = user.Id,
        Name = user.Name,
        Gender = user.Gender
    });

    //At this point, the query was not executed yet. And with that, it is possible to add new Filters
    //like the ones send by the client or even some rules from Business Logic
    //(ex: user only see other users from his country, etc)

    //Appling the queryOptions requested by the consumer
    IQueryable usersWithOptionsApplied = queryOptions.ApplyTo(usersProjected);
    IQueryable<Models.User> usersToExecute = usersWithOptionsApplied as IQueryable<Models.User>;

    //Execute the Query against the Database/Repository/Whatever with all the filters
    IEnumerable<Models.User> usersExecuted = usersToExecute.ToList();

    return Ok(usersExecuted);
}


关键点是:

1-建立查询的可能性(从数据库/存储库/任何地方获取构建器)

2-将查询投影到公开的模型(不是来自ORM的模型)

3-将从用户发送的过滤器应用于OData REST API(queryOptions)

我在此处上传的示例(在.NET中):http://multiupload.biz/2meagoxw2boa

我真的很感谢任何不愿意这样做的人。
尝试使用OData作为跨平台技术的标准方法的概念证明。

最佳答案

我真的很高兴阅读您的问题,因为这是我几个月来一直在研究的主题,希望我可以作为一个开源项目来提供。我会尝试给出一个简洁的答案,但是有很多事情要说;-)

我使用Olingo进行概念验证,并使用ElasticSearch进行后端处理,但是我想到了针对任何后端(SQL和noSQL)的开放解决方案。

有两个主要部分:


元数据配置。 Olingo提供了一个实体EdmProvider,该实体负责将托管实体的元数据提供给库。在请求处理期间将调用此方法,以将请求路由到正确的元素处理。

此级别有两种情况。您可以手动配置此元素,也可以尝试通过自动检测后端结构来自动配置。对于第一个,我们需要扩展抽象类EdmProvider。我介绍了自定义EdmProvider将基于的中间元数据,因为需要一些提示来确定实现请求的正确方法(例如,使用ElasticSearch,父/子关系...)。以下是手动配置中间元数据的示例:

MetadataBuilder builder = new MetadataBuilder();
builder.setNamespaces(Arrays.asList(new String[] { "odata" }));
builder.setValidator(validator);

TargetEntityType personDetailsAddressType
        = builder.addTargetComplexType("odata", 
                             "personDetailsAddress");
personDetailsAddressType.addField("street", "Edm.String");
personDetailsAddressType.addField("city", "Edm.String");
personDetailsAddressType.addField("state", "Edm.String");
personDetailsAddressType.addField("zipCode", "Edm.String");
personDetailsAddressType.addField("country", "Edm.String");

TargetEntityType personDetailsType
        = builder.addTargetEntityType(
                           "odata", "personDetails");
personDetailsType.addPkField("personId", "Edm.Int32");
personDetailsType.addField("age", "Edm.Int32");
personDetailsType.addField("gender", "Edm.Boolean");
personDetailsType.addField("phone", "Edm.String");
personDetailsType.addField(
             "address", "odata.personDetailsAddress");


第二种方法并非总是可行的,因为后端不必提供所有必需的元数据。对于ElasticSearch,我们需要在类型映射中添加元数据以支持它。

现在我们有了这个,我们可以专注于请求处理。
请求处理Olingo允许基于处理器处理请求。实际上,该库会将请求路由到可以处理该请求的类型的处理器。例如,如果您想对实体执行某项操作,则处理器将实现EntityCollectionProcessorCountEntityCollectionProcessor和/或
EntityProcessor将被选择并使用。然后将调用接口的正确方法。属性是一样的,...

因此,我们需要实现能够适应请求并与目标后端进行交互的处理器。在这个级别上,存在很多问题(使用Olingo序列化器/反序列化器,构建上下文URL,最终提取参数,...),并且一种好的方法似乎实现了一个通用层作为基础。后者负责在后端执行操作(读取,写入,查询等),还负责处理Olingo类型(EntityProperty,)和后端驱动程序使用的元素之间的转换(对于ElasticSearch,原始对象会命中-请参见http://www.elastic.co/guide/en/elasticsearch/client/java-api/current/client.html


因此,如果我们需要在公开的OData模型和后端模式之间建立一个间接级别,则需要在上述元数据和请求级别两者之间实现它们之间的映射。这样就可以使实体及其属性的名称不完全相同。

关于过滤器,我们可以使用UriInfo类在Olingo中轻松访问它们(请参见方法getFilterOptiongetSelectOptiongetExpandOptiongetOrderByOption
getSkipOptiongetTopOption),如下所述在处理器中:

@Override
public void readEntityCollection(final ODataRequest request,
              ODataResponse response, final UriInfo uriInfo,
              final ContentType requestedContentType)
              throws ODataApplicationException, SerializerException {
    (...)
    EntitySet entitySet = dataProvider.readEntitySet(edmEntitySet,
                uriInfo.getFilterOption(), uriInfo.getSelectOption(),
                uriInfo.getExpandOption(), uriInfo.getOrderByOption(),
                uriInfo.getSkipOption(), uriInfo.getTopOption());

    (...)
}


然后可以将所有提示传递给负责在后端创建请求的元素。这是使用ElasticSearch的示例(请注意,类QueryBuilder是用于构建查询的ElasticSearch Java的工厂):

QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
if (topOption!=null && skipOption!=null) {
    requestBuilder.setFrom(skipOption.getValue())
                  .setSize(topOption.getValue());
}


查询参数$filter的查询有点乏味,因为我们需要将初始查询转换为目标后端的查询。在类FilterOption上,我们可以访问允许访问表达式的Expression实例。以下代码描述了基于此类构建ElasticSearch查询的简化方法:

Expression expression = filterOption.getExpression();
QueryBuilder queryBuilder
           = expression.accept(new ExpressionVisitor() {
    (...)

    @Override
    public Object visitBinaryOperator(
              BinaryOperatorKind operator, Object left,
              Object right) throws ExpressionVisitException,
                                  ODataApplicationException {
        String fieldName = (String)left;
        // Simplification but not really clean code ;-)
        String value = ((String)right).substring(
                          1, right.length() - 1);
        return QueryBuilders.termQuery((String) left, right);
    }

    @Override
    public Object visitLiteral(String literal)
                throws ExpressionVisitException,
                ODataApplicationException {
        return literal;
    }

    @Override
    public Object visitMember(UriInfoResource member)
                throws ExpressionVisitException,
                ODataApplicationException {
        UriResourcePrimitiveProperty property
                   = (UriResourcePrimitiveProperty)
                          member.getUriResourceParts().get(0);
        return property.getProperty().getName();
    }
}


如果我们在查询参数description eq 'test'中使用值$filter,则将具有以下内容:

>> visitMember - member = [description]
>> visitLiteral - literal = 'test'
>> visitBinaryOperator - operator = eq, left = description, right = 'test'


另一个棘手的部分在于处理导航属性的方法,以及最终在后端对数据进行非规范化的方法。我认为这超出了您的问题范围。

随时问我是否不清楚或/,以及是否需要更多详细信息。

希望对您有帮助,
蒂埃里

关于java - 带有投影的JAVA中的OData REST API,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29137814/

相关文章:

java - ant 脚本中无法识别环境变量

java - 尽情玩耍! 2.0框架

.net - 如何将自定义 nuget feed 添加到 TeamCity 构建?

c# - MQQueue 中的 PutReplyMessage 和 PutReportMessage 的用途是什么?

c# - 在 WCF Rest 中捕获 WebFaultException 的详细信息

java - 在 Spring 操作 repo /服务/ Controller 的正确/首选方式是什么?

java - Rundeck 在多台服务器上运行命令的任何替代方案?

java - 如何检查冒号的字符串并拆分以获取第一个索引(如果它有冒号)

c# - 将 UTF-8 字符串放入字符串类型的变量中

rest - Flutter http.post()发布用户数据