diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java index dbd4e8ee2..ba8985f50 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java @@ -52,313 +52,313 @@ import org.springframework.http.converter.HttpMessageNotReadableException; */ public class PersistentEntityJackson2Module extends SimpleModule implements InitializingBean { - private static final Logger LOG = LoggerFactory.getLogger(PersistentEntityJackson2Module.class); - private static final TypeDescriptor URI_TYPE = TypeDescriptor.valueOf(URI.class); - private final ConversionService conversionService; - @Autowired - private Repositories repositories; - @Autowired - private RepositoryRestConfiguration config; - @Autowired - private UriDomainClassConverter uriDomainClassConverter; + private static final Logger LOG = LoggerFactory.getLogger(PersistentEntityJackson2Module.class); + private static final TypeDescriptor URI_TYPE = TypeDescriptor.valueOf(URI.class); + private final ConversionService conversionService; + @Autowired + private Repositories repositories; + @Autowired + private RepositoryRestConfiguration config; + @Autowired + private UriDomainClassConverter uriDomainClassConverter; - public PersistentEntityJackson2Module(ConversionService conversionService) { - super(new Version(1, 1, 0, "BUILD-SNAPSHOT", "org.springframework.data.rest", "jackson-module")); - this.conversionService = conversionService; + public PersistentEntityJackson2Module(ConversionService conversionService) { + super(new Version(1, 1, 0, "BUILD-SNAPSHOT", "org.springframework.data.rest", "jackson-module")); + this.conversionService = conversionService; - addSerializer(new ResourceSerializer()); - } + addSerializer(new ResourceSerializer()); + } - public static boolean maybeAddAssociationLink(Repositories repositories, - RepositoryRestConfiguration config, - URI baseEntityUri, - RepositoryInformation repoInfo, - ResourceMapping entityMapping, - ResourceMapping propertyMapping, - PersistentProperty persistentProperty, - List links) { - Class propertyType = persistentProperty.getType(); - if(persistentProperty.isCollectionLike() || persistentProperty.isArray()) { - propertyType = persistentProperty.getComponentType(); - } + public static boolean maybeAddAssociationLink(Repositories repositories, + RepositoryRestConfiguration config, + URI baseEntityUri, + RepositoryInformation repoInfo, + ResourceMapping entityMapping, + ResourceMapping propertyMapping, + PersistentProperty persistentProperty, + List links) { + Class propertyType = persistentProperty.getType(); + if(persistentProperty.isCollectionLike() || persistentProperty.isArray()) { + propertyType = persistentProperty.getComponentType(); + } - String propertyPath = (null != propertyMapping - ? propertyMapping.getPath() - : persistentProperty.getName()); - // In case a property mapping is specified but no path is set - if(null == propertyPath) { - propertyPath = persistentProperty.getName(); - } - String propertyRel = formatRel(config, repoInfo, persistentProperty); - if(repositories.hasRepositoryFor(propertyType)) { - // This is a managed type, generate a Link - RepositoryInformation linkedRepoInfo = repositories.getRepositoryInformationFor(propertyType); - ResourceMapping linkedRepoMapping = getResourceMapping(config, linkedRepoInfo); - if(linkedRepoMapping.isExported()) { - URI uri = buildUri(baseEntityUri, propertyPath); - Link l = new Link(uri.toString(), propertyRel); - links.add(l); - // This is an association. We added a Link. - return true; - } - } - // This is not an association. No Link was added. - return false; - } + String propertyPath = (null != propertyMapping + ? propertyMapping.getPath() + : persistentProperty.getName()); + // In case a property mapping is specified but no path is set + if(null == propertyPath) { + propertyPath = persistentProperty.getName(); + } + String propertyRel = formatRel(config, repoInfo, persistentProperty); + if(repositories.hasRepositoryFor(propertyType)) { + // This is a managed type, generate a Link + RepositoryInformation linkedRepoInfo = repositories.getRepositoryInformationFor(propertyType); + ResourceMapping linkedRepoMapping = getResourceMapping(config, linkedRepoInfo); + if(linkedRepoMapping.isExported()) { + URI uri = buildUri(baseEntityUri, propertyPath); + Link l = new Link(uri.toString(), propertyRel); + links.add(l); + // This is an association. We added a Link. + return true; + } + } + // This is not an association. No Link was added. + return false; + } - @SuppressWarnings({"unchecked"}) - @Override public void afterPropertiesSet() throws Exception { - for(Class domainType : repositories) { - addDeserializer(domainType, new ResourceDeserializer(repositories.getPersistentEntity(domainType))); - } - } + @SuppressWarnings({"unchecked"}) + @Override public void afterPropertiesSet() throws Exception { + for(Class domainType : repositories) { + addDeserializer(domainType, new ResourceDeserializer(repositories.getPersistentEntity(domainType))); + } + } - private class ResourceDeserializer extends StdDeserializer { + private class ResourceDeserializer extends StdDeserializer { - private final PersistentEntity persistentEntity; + private final PersistentEntity persistentEntity; - @SuppressWarnings({"unchecked"}) - private ResourceDeserializer(final PersistentEntity persistentEntity) { - super(persistentEntity.getType()); - this.persistentEntity = persistentEntity; - } + @SuppressWarnings({"unchecked"}) + private ResourceDeserializer(final PersistentEntity persistentEntity) { + super(persistentEntity.getType()); + this.persistentEntity = persistentEntity; + } - @SuppressWarnings({"unchecked"}) - @Override public T deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException, - JsonProcessingException { - Object entity = instantiateClass(getValueClass()); - BeanWrapper wrapper = BeanWrapper.create(entity, conversionService); - ResourceMapping domainMapping = config.getResourceMappingForDomainType(getValueClass()); + @SuppressWarnings({"unchecked"}) + @Override public T deserialize(JsonParser jp, + DeserializationContext ctxt) throws IOException, + JsonProcessingException { + Object entity = instantiateClass(getValueClass()); + BeanWrapper wrapper = BeanWrapper.create(entity, conversionService); + ResourceMapping domainMapping = config.getResourceMappingForDomainType(getValueClass()); - for(JsonToken tok = jp.nextToken(); tok != JsonToken.END_OBJECT; tok = jp.nextToken()) { - String name = jp.getCurrentName(); - switch(tok) { - case FIELD_NAME: { - if("href".equals(name)) { - URI uri = URI.create(jp.nextTextValue()); - TypeDescriptor entityType = TypeDescriptor.forObject(entity); - if(uriDomainClassConverter.matches(URI_TYPE, entityType)) { - entity = uriDomainClassConverter.convert(uri, URI_TYPE, entityType); - } - continue; - } + for(JsonToken tok = jp.nextToken(); tok != JsonToken.END_OBJECT; tok = jp.nextToken()) { + String name = jp.getCurrentName(); + switch(tok) { + case FIELD_NAME: { + if("href".equals(name)) { + URI uri = URI.create(jp.nextTextValue()); + TypeDescriptor entityType = TypeDescriptor.forObject(entity); + if(uriDomainClassConverter.matches(URI_TYPE, entityType)) { + entity = uriDomainClassConverter.convert(uri, URI_TYPE, entityType); + } + continue; + } - if("rel".equals(name)) { - // rel is currently ignored - continue; - } + if("rel".equals(name)) { + // rel is currently ignored + continue; + } - PersistentProperty persistentProperty = persistentEntity.getPersistentProperty(name); - if(null == persistentProperty) { - String errMsg = "Property '" + name + "' not found for entity " + getValueClass().getName(); - if(null == domainMapping) { - throw new HttpMessageNotReadableException(errMsg); - } - String propertyName = domainMapping.getNameForPath(name); - if(null == propertyName) { - throw new HttpMessageNotReadableException(errMsg); - } - persistentProperty = persistentEntity.getPersistentProperty(propertyName); - if(null == persistentProperty) { - throw new HttpMessageNotReadableException(errMsg); - } - } + PersistentProperty persistentProperty = persistentEntity.getPersistentProperty(name); + if(null == persistentProperty) { + String errMsg = "Property '" + name + "' not found for entity " + getValueClass().getName(); + if(null == domainMapping) { + throw new HttpMessageNotReadableException(errMsg); + } + String propertyName = domainMapping.getNameForPath(name); + if(null == propertyName) { + throw new HttpMessageNotReadableException(errMsg); + } + persistentProperty = persistentEntity.getPersistentProperty(propertyName); + if(null == persistentProperty) { + throw new HttpMessageNotReadableException(errMsg); + } + } - Object val = null; + Object val = null; - if("links".equals(name)) { - if((tok = jp.nextToken()) == JsonToken.START_ARRAY) { - while((tok = jp.nextToken()) != JsonToken.END_ARRAY) { - // Advance past the links - } - } else if(tok == JsonToken.VALUE_NULL) { - // skip null value - } else { - throw new HttpMessageNotReadableException( - "Property 'links' is not of array type. Either eliminate this property from the document or make it an array."); - } - continue; - } + if("links".equals(name)) { + if((tok = jp.nextToken()) == JsonToken.START_ARRAY) { + while((tok = jp.nextToken()) != JsonToken.END_ARRAY) { + // Advance past the links + } + } else if(tok == JsonToken.VALUE_NULL) { + // skip null value + } else { + throw new HttpMessageNotReadableException( + "Property 'links' is not of array type. Either eliminate this property from the document or make it an array."); + } + continue; + } - if(null == persistentProperty) { - // do nothing - continue; - } + if(null == persistentProperty) { + // do nothing + continue; + } - // Try and read the value of this attribute. - // The method of doing that varies based on the type of the property. - if(persistentProperty.isCollectionLike()) { - Class ctype = (Class)persistentProperty.getType(); - Collection c = (Collection)wrapper.getProperty(persistentProperty, ctype, false); - if(null == c || c == Collections.EMPTY_LIST || c == Collections.EMPTY_SET) { - if(Collection.class.isAssignableFrom(ctype)) { - c = new ArrayList(); - } else if(Set.class.isAssignableFrom(ctype)) { - c = new HashSet(); - } - } + // Try and read the value of this attribute. + // The method of doing that varies based on the type of the property. + if(persistentProperty.isCollectionLike()) { + Class ctype = (Class)persistentProperty.getType(); + Collection c = (Collection)wrapper.getProperty(persistentProperty, ctype, false); + if(null == c || c == Collections.EMPTY_LIST || c == Collections.EMPTY_SET) { + if(Collection.class.isAssignableFrom(ctype)) { + c = new ArrayList(); + } else if(Set.class.isAssignableFrom(ctype)) { + c = new HashSet(); + } + } - if((tok = jp.nextToken()) == JsonToken.START_ARRAY) { - while((tok = jp.nextToken()) != JsonToken.END_ARRAY) { - Object cval = jp.readValueAs(persistentProperty.getComponentType()); - c.add(cval); - } + if((tok = jp.nextToken()) == JsonToken.START_ARRAY) { + while((tok = jp.nextToken()) != JsonToken.END_ARRAY) { + Object cval = jp.readValueAs(persistentProperty.getComponentType()); + c.add(cval); + } - val = c; - } else if(tok == JsonToken.VALUE_NULL) { - val = null; - } else { - throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Collection."); - } - } else if(persistentProperty.isMap()) { - Class mtype = (Class)persistentProperty.getType(); - Map m = (Map)wrapper.getProperty(persistentProperty, mtype, false); - if(null == m || m == Collections.EMPTY_MAP) { - m = new HashMap(); - } + val = c; + } else if(tok == JsonToken.VALUE_NULL) { + val = null; + } else { + throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Collection."); + } + } else if(persistentProperty.isMap()) { + Class mtype = (Class)persistentProperty.getType(); + Map m = (Map)wrapper.getProperty(persistentProperty, mtype, false); + if(null == m || m == Collections.EMPTY_MAP) { + m = new HashMap(); + } - if((tok = jp.nextToken()) == JsonToken.START_OBJECT) { - do { - name = jp.getCurrentName(); - // TODO resolve domain object from URI - tok = jp.nextToken(); - Object mval = jp.readValueAs(persistentProperty.getMapValueType()); + if((tok = jp.nextToken()) == JsonToken.START_OBJECT) { + do { + name = jp.getCurrentName(); + // TODO resolve domain object from URI + tok = jp.nextToken(); + Object mval = jp.readValueAs(persistentProperty.getMapValueType()); - m.put(name, mval); - } while((tok = jp.nextToken()) != JsonToken.END_OBJECT); + m.put(name, mval); + } while((tok = jp.nextToken()) != JsonToken.END_OBJECT); - val = m; - } else if(tok == JsonToken.VALUE_NULL) { - val = null; - } else { - throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Map."); - } - } else { - if((tok = jp.nextToken()) != JsonToken.VALUE_NULL) { - val = jp.readValueAs(persistentProperty.getType()); - } - } + val = m; + } else if(tok == JsonToken.VALUE_NULL) { + val = null; + } else { + throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Map."); + } + } else { + if((tok = jp.nextToken()) != JsonToken.VALUE_NULL) { + val = jp.readValueAs(persistentProperty.getType()); + } + } - wrapper.setProperty(persistentProperty, val, false); + wrapper.setProperty(persistentProperty, val, false); - break; - } - } - } + break; + } + } + } - return (T)entity; - } - } + return (T)entity; + } + } - private class ResourceSerializer extends StdSerializer { + private class ResourceSerializer extends StdSerializer { - private ResourceSerializer() { - super(PersistentEntityResource.class); - } + private ResourceSerializer() { + super(PersistentEntityResource.class); + } - @SuppressWarnings({"unchecked"}) - @Override public void serialize(final PersistentEntityResource resource, - final JsonGenerator jgen, - final SerializerProvider provider) throws IOException, - JsonGenerationException { - if(LOG.isDebugEnabled()) { - LOG.debug("Serializing PersistentEntity " + resource.getPersistentEntity()); - } + @SuppressWarnings({"unchecked"}) + @Override public void serialize(final PersistentEntityResource resource, + final JsonGenerator jgen, + final SerializerProvider provider) throws IOException, + JsonGenerationException { + if(LOG.isDebugEnabled()) { + LOG.debug("Serializing PersistentEntity " + resource.getPersistentEntity()); + } - Object obj = resource.getContent(); + Object obj = resource.getContent(); - final PersistentEntity persistentEntity = resource.getPersistentEntity(); - final ResourceMapping entityMapping = getResourceMapping(config, persistentEntity); + final PersistentEntity persistentEntity = resource.getPersistentEntity(); + final ResourceMapping entityMapping = getResourceMapping(config, persistentEntity); - final RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(persistentEntity.getType()); - final ResourceMapping repoMapping = getResourceMapping(config, repoInfo); + final RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(persistentEntity.getType()); + final ResourceMapping repoMapping = getResourceMapping(config, repoInfo); - final BeanWrapper wrapper = BeanWrapper.create(obj, conversionService); - final Object entityId = wrapper.getProperty(persistentEntity.getIdProperty()); + final BeanWrapper wrapper = BeanWrapper.create(obj, conversionService); + final Object entityId = wrapper.getProperty(persistentEntity.getIdProperty()); - final URI baseEntityUri = buildUri(resource.getBaseUri(), - repoMapping.getPath(), - entityId.toString()); + final URI baseEntityUri = buildUri(resource.getBaseUri(), + repoMapping.getPath(), + entityId.toString()); - final List links = new ArrayList(); - // Start with ResourceProcessor-added links - links.addAll(resource.getLinks()); + final List links = new ArrayList(); + // Start with ResourceProcessor-added links + links.addAll(resource.getLinks()); - jgen.writeStartObject(); - try { - persistentEntity.doWithProperties(new PropertyHandler() { - @Override public void doWithPersistentProperty(PersistentProperty persistentProperty) { - if(persistentProperty.isIdProperty() && !config.isIdExposedFor(persistentEntity.getType())) { - return; - } - ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName()); - if(null != propertyMapping && !propertyMapping.isExported()) { - return; - } + jgen.writeStartObject(); + try { + persistentEntity.doWithProperties(new PropertyHandler() { + @Override public void doWithPersistentProperty(PersistentProperty persistentProperty) { + if(persistentProperty.isIdProperty() && !config.isIdExposedFor(persistentEntity.getType())) { + return; + } + ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName()); + if(null != propertyMapping && !propertyMapping.isExported()) { + return; + } - if(persistentProperty.isEntity() && maybeAddAssociationLink(repositories, - config, - baseEntityUri, - repoInfo, - entityMapping, - propertyMapping, - persistentProperty, - links)) { - return; - } + if(persistentProperty.isEntity() && maybeAddAssociationLink(repositories, + config, + baseEntityUri, + repoInfo, + entityMapping, + propertyMapping, + persistentProperty, + links)) { + return; + } - // Property is a normal or non-managed property. - String propertyName = (null != propertyMapping ? propertyMapping.getPath() : persistentProperty.getName()); - Object propertyValue = wrapper.getProperty(persistentProperty); - try { - jgen.writeObjectField(propertyName, propertyValue); - } catch(IOException e) { - throw new IllegalStateException(e); - } - } - }); + // Property is a normal or non-managed property. + String propertyName = (null != propertyMapping ? propertyMapping.getPath() : persistentProperty.getName()); + Object propertyValue = wrapper.getProperty(persistentProperty); + try { + jgen.writeObjectField(propertyName, propertyValue); + } catch(IOException e) { + throw new IllegalStateException(e); + } + } + }); - // Add associations as links - persistentEntity.doWithAssociations(new AssociationHandler() { - @Override public void doWithAssociation(Association association) { - PersistentProperty persistentProperty = association.getInverse(); - ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName()); - if(null != propertyMapping && !propertyMapping.isExported()) { - return; - } - if(maybeAddAssociationLink(repositories, - config, - baseEntityUri, - repoInfo, - entityMapping, - propertyMapping, - persistentProperty, - links)) { - return; - } - // Association Link was not added, probably because this isn't a managed type. Add value of property inline. - Object propertyValue = wrapper.getProperty(persistentProperty); - try { - jgen.writeObjectField(persistentProperty.getName(), propertyValue); - } catch(IOException e) { - throw new IllegalStateException(e); - } - } - }); + // Add associations as links + persistentEntity.doWithAssociations(new AssociationHandler() { + @Override public void doWithAssociation(Association association) { + PersistentProperty persistentProperty = association.getInverse(); + ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName()); + if(null != propertyMapping && !propertyMapping.isExported()) { + return; + } + if(maybeAddAssociationLink(repositories, + config, + baseEntityUri, + repoInfo, + entityMapping, + propertyMapping, + persistentProperty, + links)) { + return; + } + // Association Link was not added, probably because this isn't a managed type. Add value of property inline. + Object propertyValue = wrapper.getProperty(persistentProperty); + try { + jgen.writeObjectField(persistentProperty.getName(), propertyValue); + } catch(IOException e) { + throw new IllegalStateException(e); + } + } + }); - jgen.writeArrayFieldStart("links"); - for(Link l : links) { - jgen.writeObject(l); - } - jgen.writeEndArray(); + jgen.writeArrayFieldStart("links"); + for(Link l : links) { + jgen.writeObject(l); + } + jgen.writeEndArray(); - } catch(IllegalStateException e) { - throw (IOException)e.getCause(); - } finally { - jgen.writeEndObject(); - } - } - } + } catch(IllegalStateException e) { + throw (IOException)e.getCause(); + } finally { + jgen.writeEndObject(); + } + } + } }