All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.getperka.flatpack.Unpacker Maven / Gradle / Ivy

There is a newer version: 2.21.0
Show newest version
/*
 * #%L
 * FlatPack serialization code
 * %%
 * Copyright (C) 2012 Perka Inc.
 * %%
 * 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.
 * #L%
 */
package com.getperka.flatpack;

import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.security.Principal;
import java.util.Map;
import java.util.UUID;

import javax.inject.Inject;
import javax.inject.Provider;

import org.slf4j.Logger;

import com.getperka.flatpack.codexes.EntityCodex;
import com.getperka.flatpack.ext.Codex;
import com.getperka.flatpack.ext.DeserializationContext;
import com.getperka.flatpack.ext.TypeContext;
import com.getperka.flatpack.inject.FlatPackLogger;
import com.getperka.flatpack.inject.IgnoreUnresolvableTypes;
import com.getperka.flatpack.inject.PackScope;
import com.getperka.flatpack.util.FlatPackCollections;
import com.getperka.flatpack.util.IoObserver;
import com.getperka.flatpack.visitors.PackReader;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;

/**
 * Allows {@link FlatPackEntity} instances to be restored from their serialized representations.
 * 
 * @see FlatPack#getUnpacker()
 */
public class Unpacker {
  @Inject
  private Provider contexts;
  @Inject
  private Provider> metaCodex;
  @IgnoreUnresolvableTypes
  @Inject
  private boolean ignoreUnresolvableTypes;
  @Inject
  private IoObserver ioObserver;
  @Inject
  @FlatPackLogger
  private Logger logger;
  @Inject
  private PackScope packScope;
  @Inject
  private Provider packReaders;
  @Inject
  private TypeContext typeContext;
  @Inject
  private Visitors visitors;

  protected Unpacker() {}

/**
   * Read the properties of a single entity.
   * 
   * @param  the type of data to return
   * @param entityType the type of data to return
   * @param in a json object containing the output of a prior call to
   *          {@link Packer#append(HasUuid, Principal)
   * @param principal the identity for which the unpacking is occurring
   * @return the reified {@code entityType}
   */
  public  T read(Class entityType, JsonElement in, Principal principal) {
    if (!in.isJsonObject()) {
      throw new IllegalArgumentException("Expecting a JSON object");
    }
    packScope.enter().withPrincipal(principal);
    try {
      JsonObject chunk = in.getAsJsonObject();

      EntityCodex codex = (EntityCodex) typeContext.getCodex(entityType);

      DeserializationContext context = contexts.get();
      T toReturn = codex.allocate(chunk, context);

      PackReader packReader = packReaders.get();
      packReader.setPayload(chunk);
      visitors.visit(packReader, toReturn);

      return toReturn;
    } finally {
      packScope.exit();
    }
  }

  /**
   * Read the properties of a single entity.
   * 
   * @param  the type of data to return
   * @param entityType the type of data to return
   * @param in a Reader containing the output of a prior call to
   *          {@link Packer#append(HasUuid, Principal, java.io.Writer)}
   * @param principal the identity for which the unpacking is occurring
   * @return the reified {@code entityType}
   */
  public  T read(Class entityType, Reader in, Principal principal) {
    JsonParser parser = new JsonParser();
    JsonReader reader = new JsonReader(in);
    reader.setLenient(true);

    JsonObject chunk = parser.parse(reader).getAsJsonObject();
    return read(entityType, chunk, principal);
  }

  /**
   * Reify a {@link FlatPackEntity} from an in-memory json representation.
   * 
   * @param  the type of data to return
   * @param returnType a reference to {@code T}
   * @param in the source of the serialized data
   * @param principal the identity for which the unpacking is occurring
   * @return the reified {@link FlatPackEntity}.
   */
  public  FlatPackEntity unpack(Type returnType, JsonElement in, Principal principal)
      throws IOException {
    packScope.enter().withPrincipal(principal);
    try {
      return doUnpack(returnType, new JsonTreeReader(in), principal);
    } finally {
      packScope.exit();
    }
  }

  /**
   * Reify a {@link FlatPackEntity} from its serialized form.
   * 
   * @param  the type of data to return
   * @param returnType a reference to {@code T}
   * @param in the source of the serialized data
   * @param principal the identity for which the unpacking is occurring
   * @return the reified {@link FlatPackEntity}.
   */
  public  FlatPackEntity unpack(Type returnType, Reader in, Principal principal)
      throws IOException {
    in = ioObserver.observe(in);
    packScope.enter().withPrincipal(principal);
    try {
      return doUnpack(returnType, new JsonReader(in), principal);
    } finally {
      packScope.exit();
    }
  }

  /**
   * This method can be used with an anonymous subclass of {@link TypeReference} or with an empty
   * entity returned by a {@link FlatPackEntity} factory method.
   * 
   * @param  the type of data to return
   * @param returnType a reference to {@code T}
   * @param in the source of the serialized data
   * @param principal the identity for which the unpacking is occurring
   * @return the reified {@link FlatPackEntity}.
   * @see FlatPackEntity#collectionOf(Class)
   * @see FlatPackEntity#mapOf(Class, Class)
   * @see FlatPackEntity#stringMapOf(Class)
   */
  public  FlatPackEntity unpack(TypeReference returnType, Reader in,
      Principal principal) throws IOException {
    return unpack(returnType.getType(), in, principal);
  }

  /**
   * The guts of Unpacker.
   */
  protected  FlatPackEntity doUnpack(Type returnType, JsonReader reader, Principal principal)
      throws IOException {
    // Hold temporary state for deserialization
    DeserializationContext context = contexts.get();

    /*
     * Decoding is done as a two-pass operation since the runtime type of an allocated object cannot
     * be swizzled. The per-entity data is held as a semi-reified JsonObject to be passed off to a
     * Codex.
     */
    Map entityData = FlatPackCollections.mapForIteration();
    // Used to populate the entityData map
    JsonParser jsonParser = new JsonParser();
    /*
     * The reader is placed in lenient mode as an aid to developers, however all output will be
     * strictly formatted.
     */
    reader.setLenient(true);

    // The return value
    @SuppressWarnings("unchecked")
    FlatPackEntity toReturn = (FlatPackEntity) FlatPackEntity.create(returnType, null,
        principal);
    // Stores the single, top-level value in the payload for two-pass reification
    JsonElement value = null;

    if (reader.peek().equals(JsonToken.NULL)) {
      return toReturn;
    }

    reader.beginObject();

    while (JsonToken.NAME.equals(reader.peek())) {
      String name = reader.nextName();
      if ("data".equals(name)) {
        // data : { "fooEntity" : [ { ... }, { ... } ]
        reader.beginObject();
        while (JsonToken.NAME.equals(reader.peek())) {
          // Turn "fooEntity" into the actual FooEntity class
          String simpleName = reader.nextName();
          Class clazz = typeContext.getClass(simpleName);
          if (clazz == null) {
            if (ignoreUnresolvableTypes) {
              reader.skipValue();
              continue;
            } else {
              throw new UnsupportedOperationException("Cannot resolve type " + simpleName);
            }
          } else if (Modifier.isAbstract(clazz.getModifiers())) {
            throw new UnsupportedOperationException("A subclass of " + simpleName
              + " must be used instead");
          }
          context.pushPath("allocating " + simpleName);
          try {
            // Find the Codex for the requested entity type
            EntityCodex codex = (EntityCodex) typeContext.getCodex(clazz);

            // Take the n-many property objects and stash them for later decoding
            reader.beginArray();
            while (!JsonToken.END_ARRAY.equals(reader.peek())) {
              JsonObject chunk = jsonParser.parse(reader).getAsJsonObject();
              HasUuid entity = codex.allocate(chunk, context);
              toReturn.addExtraEntity(entity);
              entityData.put(entity, chunk.getAsJsonObject());
            }
            reader.endArray();
          } finally {
            context.popPath();
          }
        }
        reader.endObject();
      } else if ("errors".equals(name)) {
        // "errors" : { "path" : "problem", "path2" : "problem2" }
        reader.beginObject();
        while (JsonToken.NAME.equals(reader.peek())) {
          String path = reader.nextName();
          if (JsonToken.STRING.equals(reader.peek()) || JsonToken.NUMBER.equals(reader.peek())) {
            toReturn.addError(path, reader.nextString());
          } else {
            reader.skipValue();
          }
        }
        reader.endObject();
      } else if ("metadata".equals(name)) {
        reader.beginArray();

        Codex metaCodex = typeContext.getCodex(EntityMetadata.class);
        while (!JsonToken.END_ARRAY.equals(reader.peek())) {
          JsonObject metaElement = jsonParser.parse(reader).getAsJsonObject();
          PackReader packReader = packReaders.get();
          packReader.setPayload(metaElement);
          EntityMetadata meta = new EntityMetadata();
          meta.setUuid(UUID.fromString(metaElement.get("uuid").getAsString()));
          meta = visitors.getWalkers().walkSingleton(metaCodex).accept(packReader, meta);
          toReturn.addMetadata(meta);
        }

        reader.endArray();
      } else if ("value".equals(name)) {
        // Just stash the value element in case it occurs first
        value = jsonParser.parse(reader);
      } else if ("warnings".equals(name)) {
        // "warnings" : { "path" : "problem", "path2" : "problem2" }
        reader.beginObject();
        while (JsonToken.NAME.equals(reader.peek())) {
          String path = reader.nextName();
          if (JsonToken.STRING.equals(reader.peek()) || JsonToken.NUMBER.equals(reader.peek())) {
            toReturn.addWarning(path, reader.nextString());
          } else {
            reader.skipValue();
          }
        }
        reader.endObject();
      } else if (JsonToken.STRING.equals(reader.peek()) || JsonToken.NUMBER.equals(reader.peek())) {
        // Save off any other simple properties
        toReturn.putExtraData(name, reader.nextString());
      } else {
        // Ignore random other entries
        reader.skipValue();
      }
    }

    reader.endObject();
    reader.close();

    PackReader packReader = packReaders.get();
    for (Map.Entry entry : entityData.entrySet()) {
      HasUuid entity = entry.getKey();
      EntityCodex codex = (EntityCodex) typeContext
          .getCodex(entity.getClass());
      packReader.setPayload(entry.getValue());
      visitors.getWalkers().walkImmutable(codex).accept(packReader, entity);
    }

    @SuppressWarnings("unchecked")
    Codex returnCodex = (Codex) typeContext.getCodex(toReturn.getType());
    toReturn.withValue(returnCodex.read(value, context));

    for (Map.Entry entry : context.getWarnings().entrySet()) {
      toReturn.addWarning(entry.getKey().toString(), entry.getValue());
    }

    // Process metadata
    for (EntityMetadata meta : toReturn.getMetadata()) {
      if (meta.isPersistent()) {
        HasUuid entity = context.getEntity(meta.getUuid());
        if (entity instanceof PersistenceAware) {
          ((PersistenceAware) entity).markPersistent();
        }
      }
    }

    context.runPostWork();
    context.close();

    return toReturn;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy