-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for JsonProperty in QuerySpec parsing #267
JSON names mapped to Java names for QuerySpec-related paths. Can be disabled with DefaultQuerySpecUrlMapper.setMapJsonNames(..).
- Loading branch information
Showing
28 changed files
with
756 additions
and
208 deletions.
There are no files selected for viewing
1 change: 0 additions & 1 deletion
1
crnk-client/src/test/java/io/crnk/client/DynamicClientTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
crnk-core/src/main/java/io/crnk/core/engine/registry/ResourceRegistry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
183 changes: 183 additions & 0 deletions
183
crnk-core/src/main/java/io/crnk/core/queryspec/internal/DefaultQueryPathResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package io.crnk.core.queryspec.internal; | ||
|
||
import io.crnk.core.engine.document.ErrorData; | ||
import io.crnk.core.engine.http.HttpStatus; | ||
import io.crnk.core.engine.information.bean.BeanAttributeInformation; | ||
import io.crnk.core.engine.information.bean.BeanInformation; | ||
import io.crnk.core.engine.information.resource.ResourceField; | ||
import io.crnk.core.engine.information.resource.ResourceFieldType; | ||
import io.crnk.core.engine.information.resource.ResourceInformation; | ||
import io.crnk.core.engine.internal.utils.ClassUtils; | ||
import io.crnk.core.engine.internal.utils.MultivaluedMap; | ||
import io.crnk.core.engine.internal.utils.StringUtils; | ||
import io.crnk.core.engine.registry.RegistryEntry; | ||
import io.crnk.core.engine.registry.ResourceRegistry; | ||
import io.crnk.core.exception.BadRequestException; | ||
import io.crnk.core.queryspec.mapper.QueryPathResolver; | ||
import io.crnk.core.queryspec.mapper.QueryPathSpec; | ||
import io.crnk.core.queryspec.mapper.QuerySpecUrlContext; | ||
|
||
import java.lang.reflect.ParameterizedType; | ||
import java.lang.reflect.Type; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
// TODO eventually ResourceInformation should alo provide information abaout nested non-resource types to make use of reflection here unncessary | ||
public class DefaultQueryPathResolver implements QueryPathResolver { | ||
|
||
private QuerySpecUrlContext ctx; | ||
|
||
private boolean allowUnknownAttributes = false; | ||
|
||
private boolean mapJsonNames = true; | ||
|
||
@Override | ||
public void init(QuerySpecUrlContext ctx) { | ||
this.ctx = ctx; | ||
} | ||
|
||
@Override | ||
public QueryPathSpec resolve(ResourceInformation resourceInformation, List<String> attributePath, NamingType sourceNamingType, String sourceParameter) { | ||
if (attributePath == null) { | ||
// no attribute specified, query string expected, use String | ||
return new QueryPathSpec(String.class, null); | ||
} | ||
|
||
ResourceRegistry resourceRegistry = ctx.getResourceRegistry(); | ||
Type valueType = resourceInformation.getResourceClass(); | ||
|
||
SubTypeMap subTypeMap = null; | ||
|
||
List<String> targetPath = new ArrayList<>(); | ||
|
||
for (String sourceAttributePath : attributePath) { | ||
if (resourceInformation != null) { | ||
ResourceField field = sourceNamingType == NamingType.JSON ? resourceInformation.findFieldByName(sourceAttributePath) : resourceInformation.findFieldByUnderlyingName(sourceAttributePath); | ||
if (field == null) { | ||
// search subtypes for field if not available on main resource | ||
if (subTypeMap == null) { | ||
subTypeMap = new SubTypeMap(resourceRegistry); | ||
} | ||
List<ResourceInformation> subTypes = subTypeMap.findSubTypes(resourceInformation.getResourceType()); | ||
for (ResourceInformation subType : subTypes) { | ||
field = sourceNamingType == NamingType.JSON ? subType.findFieldByName(sourceAttributePath) : subType.findFieldByUnderlyingName(sourceAttributePath); | ||
if (field != null) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
if (field != null) { | ||
if (field.getResourceFieldType() == ResourceFieldType.RELATIONSHIP) { | ||
RegistryEntry entry = resourceRegistry.getEntry(field.getOppositeResourceType()); | ||
resourceInformation = entry.getResourceInformation(); | ||
valueType = resourceInformation.getResourceClass(); | ||
} else { | ||
resourceInformation = null; | ||
valueType = field.getElementType(); | ||
} | ||
targetPath.add(sourceNamingType == NamingType.JSON ? field.getUnderlyingName() : field.getJsonName()); | ||
continue; | ||
} | ||
} else { | ||
resourceInformation = null; | ||
if (valueType == Object.class) { | ||
targetPath.add(sourceAttributePath); | ||
continue; | ||
} else if (Map.class.isAssignableFrom(ClassUtils.getRawType(valueType))) { | ||
if (valueType instanceof ParameterizedType) { | ||
valueType = ((ParameterizedType) valueType).getActualTypeArguments()[1]; | ||
} else { | ||
valueType = Object.class; | ||
} | ||
targetPath.add(sourceAttributePath); | ||
continue; | ||
} else { | ||
BeanInformation beanInformation = BeanInformation.get(ClassUtils.getRawType(valueType)); | ||
BeanAttributeInformation attribute = sourceNamingType == NamingType.JSON ? beanInformation.getAttributeByJsonName(sourceAttributePath) : beanInformation.getAttribute(sourceAttributePath); | ||
if (attribute != null) { | ||
valueType = attribute.getImplementationType(); | ||
targetPath.add(sourceNamingType == NamingType.JSON ? attribute.getName() : attribute.getJsonName()); | ||
continue; | ||
} | ||
} | ||
} | ||
|
||
if (allowUnknownAttributes) { | ||
targetPath.add(sourceAttributePath); | ||
valueType = Object.class; | ||
} else { | ||
ErrorData errorData = ErrorData.builder() | ||
.setCode("UNKNOWN_PARAMETER") | ||
.setTitle("unknown parameter") | ||
.setDetail("Failed to resolve path to field '" + StringUtils.join(".", attributePath) + "'") | ||
.setSourceParameter(sourceParameter) | ||
.setStatus(String.valueOf(HttpStatus.BAD_REQUEST_400)).build(); | ||
throw new BadRequestException(HttpStatus.BAD_REQUEST_400, errorData); | ||
} | ||
} | ||
|
||
List<String> path = mapJsonNames ? targetPath : attributePath; | ||
return new QueryPathSpec(valueType, path); | ||
} | ||
|
||
@Override | ||
public boolean getAllowUnknownAttributes() { | ||
return allowUnknownAttributes; | ||
} | ||
|
||
@Override | ||
public void setAllowUnknownAttributes(boolean allowUnknownAttributes) { | ||
this.allowUnknownAttributes = allowUnknownAttributes; | ||
} | ||
|
||
@Override | ||
public boolean getMapJsonNames() { | ||
return mapJsonNames; | ||
} | ||
|
||
@Override | ||
public void setMapJsonNames(boolean mapJsonNames) { | ||
this.mapJsonNames = mapJsonNames; | ||
} | ||
|
||
class SubTypeMap { | ||
private MultivaluedMap<String, ResourceInformation> mapping = new MultivaluedMap(); | ||
|
||
public SubTypeMap(ResourceRegistry resourceRegistry) { | ||
|
||
Collection<RegistryEntry> entries = resourceRegistry.getResources(); | ||
for (RegistryEntry entry : entries) { | ||
ResourceInformation resourceInformation = entry.getResourceInformation(); | ||
String superResourceType = resourceInformation.getSuperResourceType(); | ||
if (superResourceType != null) { | ||
mapping.add(superResourceType, resourceInformation); | ||
} | ||
} | ||
} | ||
|
||
public List<ResourceInformation> findSubTypes(String resourceType) { | ||
List<ResourceInformation> results = new ArrayList<>(); | ||
findSubTypes(results, resourceType); | ||
return results; | ||
} | ||
|
||
private void findSubTypes(List<ResourceInformation> results, String resourceType) { | ||
if (mapping.containsKey(resourceType)) { | ||
List<ResourceInformation> children = mapping.getList(resourceType); | ||
// add direct descendants first | ||
for (ResourceInformation child : children) { | ||
results.add(child); | ||
} | ||
// only after add transitive children | ||
for (ResourceInformation child : children) { | ||
findSubTypes(results, child.getResourceType()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
} |
Oops, something went wrong.