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

xapi.model.api.ModelModule Maven / Gradle / Ivy

Go to download

Everything needed to run a comprehensive dev environment. Just type X_ and pick a service from autocomplete; new dev modules will be added as they are built. The only dev service not included in the uber jar is xapi-dev-maven, as it includes all runtime dependencies of maven, adding ~4 seconds to build time, and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).

The newest version!
/**
 *
 */
package xapi.model.api;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Map.Entry;
import java.util.Objects;

import xapi.collect.X_Collect;
import xapi.collect.api.IntTo;
import xapi.collect.api.StringTo;
import xapi.dev.source.CharBuffer;
import xapi.inject.X_Inject;
import xapi.model.X_Model;
import xapi.model.impl.ClusteringPrimitiveDeserializer;
import xapi.model.impl.ClusteringPrimitiveSerializer;
import xapi.source.api.CharIterator;
import xapi.source.impl.StringCharIterator;
import xapi.util.X_Debug;
import xapi.util.api.Digester;

/**
 * This class represents a module of model types; it contains the {@link ModelManifest}s for all types within this module.
 * In practice, this is used for servers to be able keep in sync with multiple clients; by having the client transmit
 * the strong hash of the ModelModule, the server can load the correct serialization policy to be able to understand
 * any number of clients.
 * 

* Whenever the server loads the ModelModule, it should save a copy into its datastore, so that, if updated, * clients who present a previous strong hash can still function correctly. The server will generally be deployed * with a module for each client compilation, which it can save while it is still on the classpath; then, should a * new version be deployed, the policy on the classpath will be stale, but can be loaded from the datastore. *

* Using this technique will allow the server to understand version-skewed clients; however, care must be taken * that a breaking change across versions will not lead to server errors. Using generic .getAllProperties() methods * will help avoid interface-based changes, but there will still be a problem when an incompatible change is made. *

* To mitigate this, we will (in the future) first create the ability to invalidate serialization policies that * are too old; this can be done with an annatation, @BreakingChange, which signals to the serialization policy * builder that the given type cannot be safely used before the current version. This annotaiton will accept * an optional hash key to state that "all versions at or before the specified key should be invalidated". *

* Once we are able to safely prevent breaking changes from causing server errors (we can just send back an * error message telling the client to update), then we will build a @MigrationStrategy, which will include * instructions on how to transform a stale model into an acceptable format. This annotation will point to * helper classes that exist which can transform a given model. *

* Using a migration strategy will easily facilitate simple field renames or removals, but for more complex situations * like adding a field that must be populated from external sources, the {@link ModelMigration} interface will take * an extra context variable (like an HttpSession) to help the server fill in any data that is not within scope. *

* When it is not possible to migrate a field, then a @BreakingChange should be used to force the client to update. * * @author James X. Nelson ([email protected], @james) * */ public class ModelModule implements Serializable { private static final long serialVersionUID = 977462783892289984L; private final StringTo manifests; private final IntTo strongNames; private String uuid; private String moduleName; private transient String serialized; public ModelModule() { manifests = X_Collect.newStringMap(ModelManifest.class); strongNames = X_Collect.newList(String.class); } public ModelModule addManifest(final ModelManifest manifest) { manifests.put(manifest.getType(), manifest); return this; } public ModelModule addStrongName(final String strongName) { strongNames.add(strongName); return this; } public ModelManifest getManifest(final String modelType) { return manifests.get(modelType); } public String[] getStrongNames() { return strongNames.toArray(); } /** * @return -> uuid */ public String getUuid() { if (uuid == null) { uuid = computeUuid(X_Inject.instance(PrimitiveSerializer.class)); } return uuid; } protected String computeUuid(final PrimitiveSerializer primitives) { // Compute the checksum of the policy itself. That checksum will become our UUID, // which will then be appended before the policy itself. final String result = calculateSerialization(primitives); final Digester digest = X_Inject.instance(Digester.class); byte[] asBytes; try { asBytes = result.getBytes("UTF-8"); } catch (final UnsupportedEncodingException e) { throw X_Debug.rethrow(e); } final byte[] uuid = digest.digest(asBytes); return digest.toString(uuid); } /** * @param uuid -> set uuid */ public void setUuid(final String uuid) { this.uuid = uuid; } public static String serialize(final ModelModule module) { final CharBuffer buffer = new CharBuffer(); serialize(buffer, module, X_Inject.instance(PrimitiveSerializer.class)); return buffer.toString(); } public static CharBuffer serialize(final CharBuffer into, final ModelModule module, final PrimitiveSerializer primitives) { // Append the uuid, as a string (using a leading size for deserialization purposes) into.append(primitives.serializeString(module.getUuid())); into.append(primitives.serializeInt(module.strongNames.size())); for (final String strongName : module.strongNames.forEach()) { into.append(primitives.serializeString(strongName)); } into.append(module.calculateSerialization(primitives)); return into; } protected String calculateSerialization(PrimitiveSerializer primitives) { if (serialized != null) { return serialized; } // We will build our policy in our own buffer, so we can safely use it to calculate our strong hash later final CharBuffer policy = new CharBuffer(); policy.append(primitives.serializeString(getModuleName())); primitives = new ClusteringPrimitiveSerializer(primitives, policy); // Directly append the policy to the result (the string is not wrapped), // however, we do append the size of the manifests so we know when to stop deserializing policy.append(primitives.serializeInt(manifests.size())); for (final ModelManifest manifest : manifests.values()) { // TODO: collect up reused strings like classnames, and append them into a "classpool", // to reduce the total size of the policy ModelManifest.serialize(policy, manifest, primitives); } serialized = policy.toString(); return serialized; } public static ModelModule deserialize(final String chars) { final StringCharIterator iter = new StringCharIterator(chars); return deserialize(iter, X_Inject.instance(PrimitiveSerializer.class)); } public static ModelModule deserialize(final CharIterator chars, PrimitiveSerializer primitives) { final ModelModule module = new ModelModule(); module.setUuid(primitives.deserializeString(chars)); int numStrongNames = primitives.deserializeInt(chars); while (numStrongNames --> 0) { module.strongNames.add(primitives.deserializeString(chars)); } module.setModuleName(primitives.deserializeString(chars)); primitives = new ClusteringPrimitiveDeserializer(primitives, chars) { @Override @SuppressWarnings("unchecked") public Class deserializeClass(final CharIterator c) { final Class cls = super.deserializeClass(c); if (Model.class.isAssignableFrom(cls)) { X_Model.getService().register(Class.class.cast(cls)); } else if (cls.isArray()) { Class component = cls.getComponentType(); while (component.isArray()) { component = cls.getComponentType(); } if (Model.class.isAssignableFrom(component)) { X_Model.getService().register(Class.class.cast(component)); } } return cls; } }; int manifests = primitives.deserializeInt(chars); while (manifests --> 0) { final ModelManifest manifest = ModelManifest.deserialize(chars, primitives); module.manifests.put(manifest.getType(), manifest); } return module; } @Override public int hashCode() { return getUuid().hashCode(); } /** * This implementation is EXTREMELY INEFFICIENT, and should only be used for unit tests; * this object should not be treated as a map key, but if you absolutely must, you should * consider using a mapping structure that allows you to provide a more efficient equality check. */ @Override public boolean equals(final Object obj) { if (obj instanceof ModelModule) { final ModelModule other = (ModelModule) obj; if (manifests.size() != other.manifests.size()) { return false; } if (!Objects.equals(moduleName, other.moduleName)) { return false; } if (!Objects.equals(getUuid(), other.getUuid())) { return false; } for (final Entry entry : manifests.entries()) { final ModelManifest compare = other.manifests.get(entry.getKey()); if (!Objects.equals(compare, entry.getValue())) { return false; } } return true; } return false; } /** * @return -> moduleName */ public String getModuleName() { return moduleName; } /** * @param moduleName -> set moduleName */ public void setModuleName(final String moduleName) { this.moduleName = moduleName; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy