Package org.springframework.data.rest.webmvc.json

Source Code of org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer

/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.rest.webmvc.json;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
import org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.UriTemplate;
import org.springframework.util.Assert;

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.std.CollectionDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerBuilder;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.type.CollectionLikeType;

/**
* Jackson 2 module to serialize and deserialize {@link PersistentEntityResource}s.
*
* @author Jon Brisbin
* @author Oliver Gierke
* @author Greg Turnquist
*/
public class PersistentEntityJackson2Module extends SimpleModule {

  private static final long serialVersionUID = -7289265674870906323L;
  private static final Logger LOG = LoggerFactory.getLogger(PersistentEntityJackson2Module.class);
  private static final TypeDescriptor URI_DESCRIPTOR = TypeDescriptor.valueOf(URI.class);

  /**
   * Creates a new {@link PersistentEntityJackson2Module} using the given {@link ResourceMappings}, {@link Repositories}
   * , {@link RepositoryRestConfiguration} and {@link UriToEntityConverter}.
   *
   * @param mappings must not be {@literal null}.
   * @param entities must not be {@literal null}.
   * @param config must not be {@literal null}.
   * @param converter must not be {@literal null}.
   */
  public PersistentEntityJackson2Module(ResourceMappings mappings, PersistentEntities entities,
      RepositoryRestConfiguration config, UriToEntityConverter converter) {

    super(new Version(2, 0, 0, null, "org.springframework.data.rest", "jackson-module"));

    Assert.notNull(mappings, "ResourceMappings must not be null!");
    Assert.notNull(entities, "Repositories must not be null!");
    Assert.notNull(config, "RepositoryRestConfiguration must not be null!");
    Assert.notNull(converter, "UriToEntityConverter must not be null!");

    AssociationLinks associationLinks = new AssociationLinks(mappings);

    addSerializer(new PersistentEntityResourceSerializer(entities, associationLinks));
    setSerializerModifier(new AssociationOmittingSerializerModifier(entities, associationLinks, config));
    setDeserializerModifier(new AssociationUriResolvingDeserializerModifier(entities, converter, associationLinks));
  }

  /**
   * Custom {@link JsonSerializer} for {@link PersistentEntityResource}s to turn associations into {@link Link}s.
   * Delegates to standard {@link Resource} serialization afterwards.
   *
   * @author Oliver Gierke
   */
  private static class PersistentEntityResourceSerializer extends StdSerializer<PersistentEntityResource> {

    private final PersistentEntities entities;
    private final AssociationLinks associationLinks;

    /**
     * Creates a new {@link PersistentEntityResourceSerializer} using the given {@link PersistentEntities} and
     * {@link AssociationLinks}.
     *
     * @param entities must not be {@literal null}.
     * @param links must not be {@literal null}.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private PersistentEntityResourceSerializer(PersistentEntities entities, AssociationLinks links) {

      super((Class) PersistentEntityResource.class);

      Assert.notNull(entities, "PersistentEntities must not be null!");
      Assert.notNull(links, "AssociationLinks must not be null!");

      this.associationLinks = links;
      this.entities = entities;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
     */
    @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());
      }

      final Link id = resource.getId();

      if (id == null) {
        throw new JsonGenerationException(String.format("No self link found resource %s!", resource));
      }

      List<Link> links = new ArrayList<Link>();
      links.addAll(resource.getLinks());

      Path basePath = new Path(id.expand().getHref());
      LinkCollectingAssociationHandler associationHandler = new LinkCollectingAssociationHandler(entities, basePath,
          associationLinks);
      resource.getPersistentEntity().doWithAssociations(associationHandler);

      for (Link link : associationHandler.getLinks()) {
        if (resource.shouldRenderLink(link)) {
          links.add(link);
        }
      }

      Resource<Object> resourceToRender = new Resource<Object>(resource.getContent(), links) {

        @JsonUnwrapped
        public Resources<?> getEmbedded() {
          return resource.getEmbeddeds();
        }
      };

      provider.defaultSerializeValue(resourceToRender, jgen);
    }
  }

  /**
   * {@link BeanSerializerModifier} to drop the property descriptors for associations.
   *
   * @author Oliver Gierke
   */
  static class AssociationOmittingSerializerModifier extends BeanSerializerModifier {

    private final PersistentEntities entities;
    private final RepositoryRestConfiguration configuration;
    private final AssociationLinks associationLinks;

    /**
     * Creates a new {@link AssociationOmittingSerializerModifier} for the given {@link PersistentEntities},
     * {@link AssociationLinks} and {@link RepositoryRestConfiguration}.
     *
     * @param entities must not be {@literal null}.
     * @param associationLinks must not be {@literal null}.
     * @param configuration must not be {@literal null}.
     */
    public AssociationOmittingSerializerModifier(PersistentEntities entities, AssociationLinks associationLinks,
        RepositoryRestConfiguration configuration) {

      Assert.notNull(entities, "PersistentEntities must not be null!");
      Assert.notNull(associationLinks, "AssociationLinks must not be null!");
      Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!");

      this.entities = entities;
      this.configuration = configuration;
      this.associationLinks = associationLinks;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.ser.BeanSerializerModifier#updateBuilder(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.BeanDescription, com.fasterxml.jackson.databind.ser.BeanSerializerBuilder)
     */
    @Override
    public BeanSerializerBuilder updateBuilder(SerializationConfig config, BeanDescription beanDesc,
        BeanSerializerBuilder builder) {

      PersistentEntity<?, ?> entity = entities.getPersistentEntity(beanDesc.getBeanClass());

      if (entity == null) {
        return builder;
      }

      List<BeanPropertyWriter> result = new ArrayList<BeanPropertyWriter>();

      for (BeanPropertyWriter writer : builder.getProperties()) {

        // Skip exported associations
        PersistentProperty<?> persistentProperty = findProperty(writer.getName(), entity, beanDesc);

        if (persistentProperty == null) {
          result.add(writer);
          continue;
        }

        if (associationLinks.isLinkableAssociation(persistentProperty)) {
          continue;
        }

        // Skip ids unless explicitly configured to expose
        if (persistentProperty.isIdProperty() && !configuration.isIdExposedFor(entity.getType())) {
          continue;
        }

        result.add(writer);
      }

      builder.setProperties(result);

      return builder;
    }

    /**
     * Returns the {@link PersistentProperty} for the property with the given final name (the name that it will be
     * rendered under eventually).
     *
     * @param finalName the output name the property will be rendered under.
     * @param entity the {@link PersistentEntity} to find the property on.
     * @param description the Jackson {@link BeanDescription}.
     * @return
     */
    private PersistentProperty<?> findProperty(String finalName, PersistentEntity<?, ?> entity,
        BeanDescription description) {

      for (BeanPropertyDefinition definition : description.findProperties()) {
        if (definition.getName().equals(finalName)) {
          return entity.getPersistentProperty(definition.getInternalName());
        }
      }

      return null;
    }
  }

  /**
   * A {@link BeanDeserializerModifier} that registers a custom {@link UriStringDeserializer} for association properties
   * of {@link PersistentEntity}s. This allows to submit URIs for those properties in request payloads, so that
   * non-optional associations can be populated on resource creation.
   *
   * @author Oliver Gierke
   */
  public static class AssociationUriResolvingDeserializerModifier extends BeanDeserializerModifier {

    private final UriToEntityConverter converter;
    private final PersistentEntities repositories;
    private final AssociationLinks associationLinks;

    /**
     * Creates a new {@link AssociationUriResolvingDeserializerModifier} using the given {@link Repositories},
     * {@link UriToEntityConverter} and {@link AssociationLinks}.
     *
     * @param repositories must not be {@literal null}.
     * @param converter must not be {@literal null}.
     * @param mappings must not be {@literal null}.
     */
    public AssociationUriResolvingDeserializerModifier(PersistentEntities repositories, UriToEntityConverter converter,
        AssociationLinks associationLinks) {

      Assert.notNull(repositories, "Repositories must not be null!");
      Assert.notNull(converter, "UriToEntityConverter must not be null!");
      Assert.notNull(associationLinks, "AssociationLinks must not be null!");

      this.repositories = repositories;
      this.converter = converter;
      this.associationLinks = associationLinks;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.deser.BeanDeserializerModifier#updateBuilder(com.fasterxml.jackson.databind.DeserializationConfig, com.fasterxml.jackson.databind.BeanDescription, com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder)
     */
    @Override
    public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc,
        BeanDeserializerBuilder builder) {

      Iterator<SettableBeanProperty> properties = builder.getProperties();
      PersistentEntity<?, ?> entity = repositories.getPersistentEntity(beanDesc.getBeanClass());

      if (entity == null) {
        return builder;
      }

      while (properties.hasNext()) {

        SettableBeanProperty property = properties.next();
        PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getName());

        if (!associationLinks.isLinkableAssociation(persistentProperty)) {
          continue;
        }

        UriStringDeserializer uriStringDeserializer = new UriStringDeserializer(persistentProperty, converter);

        if (persistentProperty.isCollectionLike()) {

          CollectionLikeType collectionType = config.getTypeFactory().constructCollectionLikeType(
              persistentProperty.getType(), persistentProperty.getActualType());
          CollectionValueInstantiator instantiator = new CollectionValueInstantiator(persistentProperty);
          CollectionDeserializer collectionDeserializer = new CollectionDeserializer(collectionType,
              uriStringDeserializer, null, instantiator);

          builder.addOrReplaceProperty(property.withValueDeserializer(collectionDeserializer), false);

        } else {
          builder.addOrReplaceProperty(property.withValueDeserializer(uriStringDeserializer), false);
        }
      }

      return builder;
    }
  }

  /**
   * Custom {@link JsonDeserializer} to interpret {@link String} values as URIs and resolve them using a
   * {@link UriToEntityConverter}.
   *
   * @author Oliver Gierke
   */
  static class UriStringDeserializer extends StdDeserializer<Object> {

    private static final long serialVersionUID = -2175900204153350125L;

    private final PersistentProperty<?> property;
    private final UriToEntityConverter converter;

    /**
     * Creates a new {@link UriStringDeserializer} for the given {@link PersistentProperty} using the given
     * {@link UriToEntityConverter}.
     *
     * @param property must not be {@literal null}.
     * @param converter must not be {@literal null}.
     */
    public UriStringDeserializer(PersistentProperty<?> property, UriToEntityConverter converter) {

      super(property.getActualType());

      this.property = property;
      this.converter = converter;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
     */
    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {

      URI uri = new UriTemplate(jp.getValueAsString()).expand();
      TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(property.getActualType());

      return converter.convert(uri, URI_DESCRIPTOR, typeDescriptor);
    }
  }

  /**
   * {@link ValueInstantiator} to create collection or map instances based on the type of the configured
   * {@link PersistentProperty}.
   *
   * @author Oliver Gierke
   */
  private static class CollectionValueInstantiator extends ValueInstantiator {

    private final PersistentProperty<?> property;

    /**
     * Creates a new {@link CollectionValueInstantiator} for the given {@link PersistentProperty}.
     *
     * @param property must not be {@literal null} and must be a collection.
     */
    public CollectionValueInstantiator(PersistentProperty<?> property) {

      Assert.notNull(property, "Property must not be null!");
      Assert.isTrue(property.isCollectionLike() || property.isMap(), "Property must be a collection or map property!");

      this.property = property;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.deser.ValueInstantiator#getValueTypeDesc()
     */
    @Override
    public String getValueTypeDesc() {
      return property.getType().getName();
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.deser.ValueInstantiator#createUsingDefault(com.fasterxml.jackson.databind.DeserializationContext)
     */
    @Override
    public Object createUsingDefault(DeserializationContext ctxt) throws IOException, JsonProcessingException {

      Class<?> collectionOrMapType = property.getType();

      return property.isMap() ? CollectionFactory.createMap(collectionOrMapType, 0) : CollectionFactory
          .createCollection(collectionOrMapType, 0);
    }
  }
}
TOP

Related Classes of org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.