/*
* 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.BufferedWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
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.AbstractClob;
import com.amazon.carbonado.lob.Clob;
/**
* After loading a replica, replaces all Clobs with ReplicatedClobs.
*
* @author Brian S O'Neill
*/
class ClobReplicationTrigger<S extends Storable> extends Trigger<S> {
/**
* Returns null if no Clobs need to be replicated.
*/
static <S extends Storable> ClobReplicationTrigger<S> create(Storage<S> masterStorage) {
Map<String, ? extends StorableProperty<S>> properties =
StorableIntrospector.examine(masterStorage.getStorableType()).getDataProperties();
List<String> clobNames = new ArrayList<String>(2);
for (StorableProperty<S> property : properties.values()) {
if (property.getType() == Clob.class) {
clobNames.add(property.getName());
}
}
if (clobNames.size() == 0) {
return null;
}
return new ClobReplicationTrigger<S>(masterStorage,
clobNames.toArray(new String[clobNames.size()]));
}
private final Storage<S> mMasterStorage;
private final String[] mClobNames;
private ClobReplicationTrigger(Storage<S> masterStorage, String[] clobNames) {
mMasterStorage = masterStorage;
mClobNames = clobNames;
}
@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 : mClobNames) {
if (!replica.isPropertySupported(name)) {
continue;
}
Clob replicaClob = (Clob) replica.getPropertyValue(name);
if (replicaClob != null) {
if (replicaClob instanceof ClobReplicationTrigger.Replicated) {
if (((Replicated) replicaClob).parent() == this) {
continue;
}
}
Replicated clob = new Replicated(name, replica, replicaClob);
replica.setPropertyValue(name, clob);
}
}
replica.markAllPropertiesClean();
}
/**
* Writes go to master property first, and then to replica.
*/
class Replicated extends AbstractClob {
private static final int DEFAULT_BUFFER_SIZE = 4000;
private final String mClobName;
private final S mReplica;
private final Clob mReplicaClob;
private Clob mMasterClob;
private boolean mMasterClobLoaded;
Replicated(String clobName, S replica, Clob replicaClob) {
mClobName = clobName;
mReplica = replica;
mReplicaClob = replicaClob;
}
public Reader openReader() throws FetchException {
return mReplicaClob.openReader();
}
public Reader openReader(long pos) throws FetchException {
return mReplicaClob.openReader(pos);
}
public Reader openReader(long pos, int bufferSize) throws FetchException {
return mReplicaClob.openReader(pos, bufferSize);
}
public long getLength() throws FetchException {
return mReplicaClob.getLength();
}
@Override
public String asString() throws FetchException {
return mReplicaClob.asString();
}
public Writer openWriter() throws PersistException {
Clob masterClob = masterClob();
if (masterClob == null) {
return mReplicaClob.openWriter();
} else {
return openWriter(masterClob, 0, DEFAULT_BUFFER_SIZE);
}
}
public Writer openWriter(long pos) throws PersistException {
Clob masterClob = masterClob();
if (masterClob == null) {
return mReplicaClob.openWriter(pos);
} else {
return openWriter(masterClob, pos, DEFAULT_BUFFER_SIZE);
}
}
public Writer openWriter(long pos, int bufferSize) throws PersistException {
Clob masterClob = masterClob();
if (masterClob == null) {
return mReplicaClob.openWriter(pos, bufferSize);
} else {
return openWriter(masterClob, pos, bufferSize);
}
}
private Writer openWriter(Clob masterClob, long pos, int bufferSize)
throws PersistException
{
if (bufferSize < DEFAULT_BUFFER_SIZE) {
bufferSize = DEFAULT_BUFFER_SIZE;
}
Writer masterOut = masterClob.openWriter(pos, 0);
Writer replicaOut = mReplicaClob.openWriter(pos, 0);
return new BufferedWriter(new Copier(masterOut, replicaOut), bufferSize);
}
public void setLength(long length) throws PersistException {
Clob masterClob = masterClob();
if (masterClob != null) {
masterClob.setLength(length);
}
mReplicaClob.setLength(length);
}
public Object getLocator() {
return mReplicaClob.getLocator();
}
@Override
public int hashCode() {
return mReplicaClob.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof ClobReplicationTrigger.Replicated) {
Replicated other = (Replicated) obj;
return parent() == other.parent() &&
mReplicaClob.equals(other.mReplicaClob);
}
return false;
}
@Override
public String toString() {
Object locator = getLocator();
return locator == null ? super.toString() : ("ReplicatedClob@" + locator);
}
ClobReplicationTrigger parent() {
return ClobReplicationTrigger.this;
}
/**
* Returns null if not supported.
*/
private Clob masterClob() throws PersistException {
Clob masterClob = mMasterClob;
if (mMasterClobLoaded) {
return masterClob;
}
S master = mMasterStorage.prepare();
mReplica.copyPrimaryKeyProperties(master);
try {
// FIXME: handle missing master with resync
master.load();
if (master.isPropertySupported(mClobName)) {
masterClob = (Clob) master.getPropertyValue(mClobName);
if (masterClob == null) {
// FIXME: perform resync, but still throw exception
throw new PersistNoneException("Master Clob is null: " + mClobName);
}
}
mMasterClob = masterClob;
mMasterClobLoaded = true;
return masterClob;
} catch (FetchException e) {
throw e.toPersistException();
}
}
}
private static class Copier extends Writer {
private final Writer mReplicaOut;
private final Writer mMasterOut;
Copier(Writer master, Writer replica) {
mMasterOut = master;
mReplicaOut = replica;
}
@Override
public void write(int c) throws IOException {
mMasterOut.write(c);
mReplicaOut.write(c);
}
@Override
public void write(char[] c, int off, int len) throws IOException {
mMasterOut.write(c, off, len);
mReplicaOut.write(c, off, len);
}
@Override
public void flush() throws IOException {
mMasterOut.flush();
mReplicaOut.flush();
}
@Override
public void close() throws IOException {
mMasterOut.close();
mReplicaOut.close();
}
}
}