/* * Copyright 2008-2012 Amazon Technologies, Inc. or its affiliates. * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks * of Amazon Technologies, Inc. or its affiliates. All rights reserved. * * 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 com.amazon.carbonado.repo.replicated; import java.io.BufferedOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Map; import java.util.List; import com.amazon.carbonado.FetchException; import com.amazon.carbonado.PersistException; import com.amazon.carbonado.PersistNoneException; import com.amazon.carbonado.Storable; import com.amazon.carbonado.Storage; import com.amazon.carbonado.Trigger; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.lob.AbstractBlob; import com.amazon.carbonado.lob.Blob; /** * After loading a replica, replaces all Blobs with ReplicatedBlobs. * * @author Brian S O'Neill */ class BlobReplicationTrigger<S extends Storable> extends Trigger<S> { /** * Returns null if no Blobs need to be replicated. */ static <S extends Storable> BlobReplicationTrigger<S> create(Storage<S> masterStorage) { Map<String, ? extends StorableProperty<S>> properties = StorableIntrospector.examine(masterStorage.getStorableType()).getDataProperties(); List<String> blobNames = new ArrayList<String>(2); for (StorableProperty<S> property : properties.values()) { if (property.getType() == Blob.class) { blobNames.add(property.getName()); } } if (blobNames.size() == 0) { return null; } return new BlobReplicationTrigger<S>(masterStorage, blobNames.toArray(new String[blobNames.size()])); } private final Storage<S> mMasterStorage; private final String[] mBlobNames; private BlobReplicationTrigger(Storage<S> masterStorage, String[] blobNames) { mMasterStorage = masterStorage; mBlobNames = blobNames; } @Override public void afterInsert(S replica, Object state) { afterLoad(replica); } @Override public void afterUpdate(S replica, Object state) { afterLoad(replica); } @Override public void afterLoad(S replica) { for (String name : mBlobNames) { if (!replica.isPropertySupported(name)) { continue; } Blob replicaBlob = (Blob) replica.getPropertyValue(name); if (replicaBlob != null) { if (replicaBlob instanceof BlobReplicationTrigger.Replicated) { if (((Replicated) replicaBlob).parent() == this) { continue; } } Replicated blob = new Replicated(name, replica, replicaBlob); replica.setPropertyValue(name, blob); } } replica.markAllPropertiesClean(); } /** * Writes go to master property first, and then to replica. */ class Replicated extends AbstractBlob { private static final int DEFAULT_BUFFER_SIZE = 4000; private final String mBlobName; private final S mReplica; private final Blob mReplicaBlob; private Blob mMasterBlob; private boolean mMasterBlobLoaded; Replicated(String blobName, S replica, Blob replicaBlob) { mBlobName = blobName; mReplica = replica; mReplicaBlob = replicaBlob; } public InputStream openInputStream() throws FetchException { return mReplicaBlob.openInputStream(); } public InputStream openInputStream(long pos) throws FetchException { return mReplicaBlob.openInputStream(pos); } public InputStream openInputStream(long pos, int bufferSize) throws FetchException { return mReplicaBlob.openInputStream(pos, bufferSize); } public long getLength() throws FetchException { return mReplicaBlob.getLength(); } @Override public String asString() throws FetchException { return mReplicaBlob.asString(); } @Override public String asString(String charsetName) throws FetchException { return mReplicaBlob.asString(charsetName); } @Override public String asString(Charset charset) throws FetchException { return mReplicaBlob.asString(charset); } public OutputStream openOutputStream() throws PersistException { Blob masterBlob = masterBlob(); if (masterBlob == null) { return mReplicaBlob.openOutputStream(); } else { return openOutputStream(masterBlob, 0, DEFAULT_BUFFER_SIZE); } } public OutputStream openOutputStream(long pos) throws PersistException { Blob masterBlob = masterBlob(); if (masterBlob == null) { return mReplicaBlob.openOutputStream(pos); } else { return openOutputStream(masterBlob, pos, DEFAULT_BUFFER_SIZE); } } public OutputStream openOutputStream(long pos, int bufferSize) throws PersistException { Blob masterBlob = masterBlob(); if (masterBlob == null) { return mReplicaBlob.openOutputStream(pos, bufferSize); } else { return openOutputStream(masterBlob, pos, bufferSize); } } private OutputStream openOutputStream(Blob masterBlob, long pos, int bufferSize) throws PersistException { if (bufferSize < DEFAULT_BUFFER_SIZE) { bufferSize = DEFAULT_BUFFER_SIZE; } OutputStream masterOut = masterBlob.openOutputStream(pos, 0); OutputStream replicaOut = mReplicaBlob.openOutputStream(pos, 0); return new BufferedOutputStream(new Copier(masterOut, replicaOut), bufferSize); } public void setLength(long length) throws PersistException { Blob masterBlob = masterBlob(); if (masterBlob != null) { masterBlob.setLength(length); } mReplicaBlob.setLength(length); } public Object getLocator() { return mReplicaBlob.getLocator(); } @Override public int hashCode() { return mReplicaBlob.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof BlobReplicationTrigger.Replicated) { Replicated other = (Replicated) obj; return parent() == other.parent() && mReplicaBlob.equals(other.mReplicaBlob); } return false; } @Override public String toString() { Object locator = getLocator(); return locator == null ? super.toString() : ("ReplicatedBlob@" + locator); } BlobReplicationTrigger parent() { return BlobReplicationTrigger.this; } /** * Returns null if not supported. */ private Blob masterBlob() throws PersistException { Blob masterBlob = mMasterBlob; if (mMasterBlobLoaded) { return masterBlob; } S master = mMasterStorage.prepare(); mReplica.copyPrimaryKeyProperties(master); try { // FIXME: handle missing master with resync master.load(); if (master.isPropertySupported(mBlobName)) { masterBlob = (Blob) master.getPropertyValue(mBlobName); if (masterBlob == null) { // FIXME: perform resync, but still throw exception throw new PersistNoneException("Master Blob is null: " + mBlobName); } } mMasterBlob = masterBlob; mMasterBlobLoaded = true; return masterBlob; } catch (FetchException e) { throw e.toPersistException(); } } } private static class Copier extends OutputStream { private final OutputStream mReplicaOut; private final OutputStream mMasterOut; Copier(OutputStream master, OutputStream replica) { mMasterOut = master; mReplicaOut = replica; } @Override public void write(int b) throws IOException { mMasterOut.write(b); mReplicaOut.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { mMasterOut.write(b, off, len); mReplicaOut.write(b, off, len); } @Override public void flush() throws IOException { mMasterOut.flush(); mReplicaOut.flush(); } @Override public void close() throws IOException { mMasterOut.close(); mReplicaOut.close(); } } }