/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.jboss.shrinkwrap.impl.base.serialization;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.Assignable;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.api.serialization.ZipSerializableView;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.impl.base.Validate;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.jboss.shrinkwrap.spi.Identifiable;

Implementation of a Serializable view of Archives, backed by ZIP en/decoding the contents during serialization/deserialization. Defines the wire protocol and must remain backwards-compatible.
Author:ALR
Version:$Revision: $
/** * Implementation of a {@link Serializable} view of {@link Archive}s, backed by ZIP en/decoding the contents during * serialization/deserialization. Defines the wire protocol and must remain backwards-compatible. * * @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a> * @version $Revision: $ */
public class ZipSerializableViewImpl implements ZipSerializableView { // -------------------------------------------------------------------------------------|| // Class Members ----------------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
serialVersionUID
/** * serialVersionUID */
private static final long serialVersionUID = 1L;
Logger
/** * Logger */
private static final Logger log = Logger.getLogger(ZipSerializableViewImpl.class.getName()); // -------------------------------------------------------------------------------------|| // Instance Members -------------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
Name of the archive; to be serialized
/** * Name of the archive; to be serialized */
private final String name;
String form of the ID
/** * String form of the ID */
private transient String id;
Underlying archive. Won't be directly serialized; instead we'll encode it as ZIP and send that
/** * Underlying archive. Won't be directly serialized; instead we'll encode it as ZIP and send that */
private transient Archive<?> archive; // -------------------------------------------------------------------------------------|| // Constructor ------------------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
Creates a new instance, wrapping the specified Archive
/** * Creates a new instance, wrapping the specified {@link Archive} */
public ZipSerializableViewImpl(final Archive<?> archive) { Validate.notNull(archive, "Archive must be specified"); final String name = archive.getName(); Validate.notNullOrEmpty(name, "Name of archive must be specified"); this.archive = archive; this.name = name; this.id = archive.getId(); } // -------------------------------------------------------------------------------------|| // Required Implementations -----------------------------------------------------------|| // -------------------------------------------------------------------------------------||
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see org.jboss.shrinkwrap.api.Assignable#as(java.lang.Class) */
@Override public <TYPE extends Assignable> TYPE as(final Class<TYPE> clazz) { return archive.as(clazz); } // -------------------------------------------------------------------------------------|| // Serialization ----------------------------------------------------------------------|| // -------------------------------------------------------------------------------------||
Serializes the invocation with a custom form
@serialDataAfter all non-transient fields are written, we send the Archive contents encoded as ZIP.
/** * Serializes the invocation with a custom form * * @serialData After all non-transient fields are written, we send the {@link Archive} contents encoded as ZIP. */
private void writeObject(final ObjectOutputStream out) throws IOException { // Default write of non-transient fields out.defaultWriteObject(); // Write as ZIP final InputStream in = archive.as(ZipExporter.class).exportAsInputStream(); try { IOUtil.copy(in, out); // Don't close the outstream // Write the ID explicitly out.writeObject(id); } finally { // In case we get an InputStream type that supports closing in.close(); } // Log if (log.isLoggable(Level.FINER)) { log.finer("Wrote archive: " + archive.toString()); } }
Deserializes according to the custom form defined by writeObject.writeObject(ObjectOutputStream)
/** * Deserializes according to the custom form defined by {@link ZipSerializableImpl#writeObject(ObjectOutputStream)} */
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { // Get default form in.defaultReadObject(); // Create new Archive final String name = this.name; final ZipImporter archive = ShrinkWrap.create(ZipImporter.class, name); // Read in archive.importFrom(in); // Set this.archive = archive.as(JavaArchive.class); // Log if (log.isLoggable(Level.FINER)) { log.finer("Read in archive: " + archive.toString()); } /* * Leave this bit here. * * After reading in the ZIP stream contents, we need to also get to the EOF marker (which is not read in by the * ZIP import process because it's the ZIP header, not part of the true contents. Putting this loop here ensures * we reach the marker, which is *not* the true end of the stream. Object data may be read again after here via * something like: * * in.readObject(); * * Without this loop we'll get an OptionalDataException when trying to read more objects in from the stream. In * the future we may add state which needs to be part of the serialization protocol, and things need to stay in * order, so they'll be added *after* the archive ZIP contents. Thus we must be able to read them. */ while (in.read() != -1) { } // We've now added an ID to archives, so add this into the wire protocol try { final String id = (String) in.readObject(); this.archive.as(Identifiable.class).setId(id); } catch (final OptionalDataException ode) { /* * Swallow; this occurs when an older wire format (without notion of ID) attempts to send an archive to a * new format. The new archive will have the ID assigned to it at creation time, and we'll ignore that we * can't read in the remote one from this stream (as the remote one from the old version does not have an ID * to send */ if (log.isLoggable(Level.FINER)) { log.finer("Detected an older version of the archive sent over the wire; no ID was sent. " + "Ignoring and using the default ID for this archive: " + archive.toString()); } } } }