[英]OData REST API in JAVA with 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允許基於處理器處理請求。 實際上,該庫會將請求路由到可以處理該請求的類型的處理器。 例如,如果要在實體上執行CountEntityCollectionProcessor
,將選擇並使用實現EntityCollectionProcessor
, CountEntityCollectionProcessor
和/或EntityProcessor
的處理器。 然后將調用接口的正確方法。 屬性是一樣的,...
因此,我們需要實現能夠適應請求並與目標后端進行交互的處理器。 在這個級別上,有很多管道(使用Olingo序列化器/反序列化器,構建上下文URL,最終提取參數,...),並且一種好的方法似乎實現了一個通用層作為基礎。 后者負責在后端執行操作(讀取,寫入,查詢等),還負責處理Olingo的類型( Entity
, Property
)和后端驅動程序使用的元素之間的轉換(對於ElasticSearch,原始對象,點擊數-參見http://www.elastic.co/guide/en/elasticsearch/client/java-api/current/client.html )。
因此,如果我們需要在公開的OData模型和后端模式之間建立一個間接級別,則需要在上述元數據和請求級別兩者之間實現它們之間的映射。 這樣就可以使實體及其屬性的名稱不完全相同。
關於過濾器,我們可以使用類UriInfo
(請參見方法getFilterOption
, getSelectOption
, getExpandOption
, getOrderByOption
, getSkipOption
和getTopOption
)在Olingo中輕松訪問它們,如下所述在處理器中:
@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();
}
}
如果我們在查詢參數$filter
使用值description eq 'test'
,我們將得到類似的結果:
>> visitMember - member = [description]
>> visitLiteral - literal = 'test'
>> visitBinaryOperator - operator = eq, left = description, right = 'test'
另一個棘手的部分在於處理導航屬性的方法,以及最終在后端對數據進行非規范化的方法。 我認為這超出了您的問題范圍。
隨時問我是否不清楚或/,以及是否需要更多詳細信息。
希望對您有幫助,蒂埃里
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.