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

com.emc.codec.CodecChain Maven / Gradle / Ivy

/*
 * Copyright (c) 2015, EMC Corporation.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * + Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * + Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * + The name of EMC Corporation may not be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

package com.emc.codec;

import java.io.*;
import java.util.*;

public class CodecChain {
    public static final String META_TRANSFORM_MODE = "x-emc-transform-mode";
    public static final String META_TRANSFORM_COMPLETE = "x-emc-transform-complete";

    // apparently ServiceLoader instances are not thread-safe and we don't want to synchronize on a static property or
    // load an instance each time a codec is constructed (potentially in every read request from the encryption client)
    private static ThreadLocal> codecLoader = new ThreadLocal>();

    private static ServiceLoader getCodecLoader() {
        if (codecLoader.get() == null) {
            codecLoader.set(ServiceLoader.load(AbstractCodec.class));
        }
        return codecLoader.get();
    }

    public static String[] getEncodeSpecs(Map metaMap) {
        if (metaMap == null) return null;
        String specString = metaMap.get(META_TRANSFORM_MODE);
        if (specString == null) return null;
        return specString.split(",");
    }

    public static void addEncodeSpec(Map metaMap, String encodeSpec) {
        String specString = metaMap.get(META_TRANSFORM_MODE);
        if (specString != null && specString.length() > 0) encodeSpec = specString + "," + encodeSpec;
        metaMap.put(META_TRANSFORM_MODE, encodeSpec);
    }

    public static void removeEncodeSpec(Map metaMap, String encodeSpec) {
        String specString = metaMap.get(META_TRANSFORM_MODE);
        if (specString.equals(encodeSpec)) {
            metaMap.remove(META_TRANSFORM_MODE);
        } else {
            if (!specString.endsWith("," + encodeSpec))
                throw new UnsupportedOperationException("the encode chain does not include " + encodeSpec);

            metaMap.put(META_TRANSFORM_MODE,
                    specString.substring(0, specString.length() - (encodeSpec.length() + 1)));
        }
    }

    private List codecs;
    private Map specMap = new HashMap();
    private Map properties = new HashMap();

    public CodecChain(String... encodeSpecs) {
        codecs = new ArrayList();

        for (String encodeSpec : encodeSpecs) {
            boolean codecFound = false;
            for (AbstractCodec codec : getCodecLoader()) {
                if (codec.canDecode(encodeSpec)) {
                    codecFound = true;
                    codecs.add(codec);
                    specMap.put(codec, encodeSpec);
                }
            }
            if (!codecFound) throw new IllegalArgumentException("Unsupported encoder: " + encodeSpec);
        }
    }

    public CodecChain(AbstractCodec... codecs) {
        this(Arrays.asList(codecs), null);
    }

    public CodecChain(List codecs, Map properties) {
        this.codecs = codecs;
        if (properties != null) this.properties = properties;
        Collections.sort(codecs); // make sure codecs are sorted by priority
    }

    public boolean isSizePredictable() {
        for (AbstractCodec codec : codecs) {
            if (!codec.isSizePredictable()) return false;
        }
        return true;
    }

    public long getEncodedSize(long originalSize) {
        long encodedSize = originalSize;
        for (AbstractCodec codec : codecs) {
            String encodeSpec = specMap.get(codec);
            encodedSize = encodeSpec == null ? codec.getEncodedSize(originalSize, properties)
                    : codec.getEncodedSize(originalSize, encodeSpec, properties);
        }
        return encodedSize;
    }

    public OutputStream getEncodeStream(OutputStream targetStream, Map completeMetaMap) {
        for (int i = codecs.size() - 1; i >= 0; i--) { // wrap encode output streams in reverse order
            AbstractCodec codec = codecs.get(i);
            String encodeSpec = specMap.get(codec);
            if (encodeSpec == null) targetStream = codec.getEncodingStream(targetStream, properties);
            else targetStream = codec.getEncodingStream(targetStream, encodeSpec, properties);
        }

        return new MetaAddingOutputStream((EncodeOutputStream) targetStream, completeMetaMap);
    }

    public InputStream getEncodeStream(InputStream sourceStream, Map completeMetaMap) {
        for (AbstractCodec codec : codecs) { // wrap encode input streams in natural order
            String encodeSpec = specMap.get(codec);
            if (encodeSpec == null) sourceStream = codec.getEncodingStream(sourceStream, properties);
            else sourceStream = codec.getEncodingStream(sourceStream, encodeSpec, properties);
        }

        return new MetaAddingInputStream((EncodeInputStream) sourceStream, completeMetaMap);
    }

    @SuppressWarnings("unchecked")
    public OutputStream getDecodeStream(OutputStream targetStream, Map completeMetaMap) {
        List metadataList = getEncodeMetadataList(completeMetaMap);

        // wrap decode output streams in natural order
        for (int i = 0; i < codecs.size(); i++) {
            AbstractCodec codec = codecs.get(i);
            EncodeMetadata metadata = metadataList.get(i);
            targetStream = codec.getDecodingStream(targetStream, metadata, properties);
        }

        // remove encode metadata from map (we don't need it anymore)
        removeEncodeMetadata(completeMetaMap, metadataList);

        return targetStream;
    }

    @SuppressWarnings("unchecked")
    public InputStream getDecodeStream(InputStream sourceStream, Map completeMetaMap) {
        List metadataList = getEncodeMetadataList(completeMetaMap);

        // wrap decode input streams in reverse order
        for (int i = codecs.size() - 1; i >= 0; i--) {
            AbstractCodec codec = codecs.get(i);
            EncodeMetadata metadata = metadataList.get(i);
            sourceStream = codec.getDecodingStream(sourceStream, metadata, properties);
        }

        // remove encode metadata from map (we don't need it anymore)
        removeEncodeMetadata(completeMetaMap, metadataList);

        return sourceStream;
    }

    public List getEncodeMetadataList(Map completeMetaMap) {
        String[] encodeSpecs = getEncodeSpecs(completeMetaMap);
        List metadataList = new ArrayList();

        // if we have X codecs, we can only decode the last X encode specs.
        // this may leave the object still encoded somehow, but perhaps that is a valid use-case
        encodeSpecs = Arrays.copyOfRange(encodeSpecs, encodeSpecs.length - codecs.size(), encodeSpecs.length);

        for (int i = 0; i < codecs.size(); i++) {
            AbstractCodec codec = codecs.get(i);
            String encodeSpec = encodeSpecs[i];
            if (!codec.canDecode(encodeSpec))
                throw new RuntimeException("this codec chain cannot decode the following encode list:\n" + Arrays.toString(encodeSpecs));

            EncodeMetadata metadata = codec.createEncodeMetadata(encodeSpec, completeMetaMap);
            metadataList.add(metadata);
        }

        return metadataList;
    }

    public class MetaAddingOutputStream extends FilterOutputStream {
        private EncodeOutputStream firstOutputStream;
        private Map metaMap;

        /**
         * wrap head of chain for output streams
         */
        public MetaAddingOutputStream(EncodeOutputStream firstOutputStream, Map metaMap) {
            super(firstOutputStream);
            this.firstOutputStream = firstOutputStream;
            this.metaMap = metaMap;
            addEncodeMetadata(metaMap, firstOutputStream, true);
        }

        // Override because FilterOutputStream does not do array writes.
        @Override
        public void write(byte[] b) throws IOException {
            out.write(b);
        }

        // Override because FilterOutputStream does not do array writes.
        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            out.write(b, off, len);
        }

        @Override
        public void close() throws IOException {
            super.close();
            addEncodeMetadata(metaMap, firstOutputStream, false);
        }
    }

    public class MetaAddingInputStream extends FilterInputStream {
        private EncodeInputStream lastInputStream;
        private Map metaMap;

        /**
         * wrap tail of chain for input streams
         */
        public MetaAddingInputStream(EncodeInputStream lastInputStream, Map metaMap) {
            super(lastInputStream);
            this.lastInputStream = lastInputStream;
            this.metaMap = metaMap;
            addEncodeMetadata(metaMap, lastInputStream, true);
        }

        @Override
        public void close() throws IOException {
            super.close();
            addEncodeMetadata(metaMap, lastInputStream, false);
        }
    }

    protected void addEncodeMetadata(Map metaMap, EncodeStream encodeStream, boolean addEncodeSpec) {

        // add all encode metadata to the meta map
        boolean complete = true;
        encodeStream = encodeStream.getChainHead(); // make sure we start at the head of the chain
        do {
            EncodeMetadata metadata = encodeStream.getEncodeMetadata();
            if (!metadata.isComplete()) complete = false;
            metaMap.putAll(metadata.toMap());
            if (addEncodeSpec) addEncodeSpec(metaMap, metadata.getEncodeSpec());
            encodeStream = encodeStream.getNext();
        } while (encodeStream != null);

        metaMap.put(META_TRANSFORM_COMPLETE, "" + complete); // this flag indicates whether the transform is complete
    }

    public void removeEncodeMetadata(Map metaMap, List encodeMetaList) {
        // remove decoded specs from the chain spec and remove related metadata
        // must remove in reverse order (tail to head)
        for (int i = encodeMetaList.size() - 1; i >= 0; i--) {
            EncodeMetadata metadata = encodeMetaList.get(i);
            removeEncodeSpec(metaMap, metadata.getEncodeSpec()); // remove spec from end of chain
            metaMap.keySet().removeAll(metadata.toMap().keySet()); // remove encode metadata
            metaMap.remove(META_TRANSFORM_COMPLETE); // remove completion flag
        }
    }

    public Map getProperties() {
        return properties;
    }

    public void setProperties(Map properties) {
        this.properties = properties;
    }

    public CodecChain withProperties(Map properties) {
        setProperties(properties);
        return this;
    }

    public void addProperty(String name, Object value) {
        properties.put(name, value);
    }

    public CodecChain withProperty(String name, Object value) {
        addProperty(name, value);
        return this;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy