/**
* Copyright 2011-2013 Terracotta, Inc.
* Copyright 2011-2013 Oracle, Inc.
*
* 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.jsr107.tck.integration;
import org.jsr107.tck.support.CacheClient;
import org.jsr107.tck.support.Operation;
import org.jsr107.tck.support.Server;
import javax.cache.Cache;
import javax.cache.integration.CacheWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutionException;
/**
* A {@link CacheWriter} that delegates requests to a {@link CacheWriterServer}.
*
* @param <K> the type of keys
* @param <V> the type of values
* @author Brian Oliver
* @author Joe Fialli
*/
public class CacheWriterClient<K, V> extends CacheClient implements CacheWriter<K, V> {
private CacheWriterServer<K,V> shortCircuitServer;
/**
* Constructs a {@link CacheWriterClient}.
*
* @param address the {@link InetAddress} on which to connect to the {@link CacheWriterServer}
* @param port the port to which to connect to the {@link CacheWriterServer}
*/
public CacheWriterClient(InetAddress address, int port) {
super(address, port);
}
@Override
protected boolean checkDirectCallsPossible() {
Server server = Server.lookupServerAtLocalMachine(port);
if (server != null) {
shortCircuitServer = ((CacheWriterServer) server);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void write(Cache.Entry<? extends K, ? extends V> entry) {
if (isDirectCallable()) {
shortCircuitServer.getCacheWriter().write(entry);
return;
}
getClient().invoke(new WriteOperation<>(entry));
}
/**
* {@inheritDoc}
*/
@Override
public void writeAll(Collection<Cache.Entry<? extends K, ? extends V>> entries) {
if (isDirectCallable()) {
shortCircuitServer.getCacheWriter().writeAll(entries);
return;
}
getClient().invoke(new WriteAllOperation<>(entries));
}
@Override
public void delete(Object key) {
if (isDirectCallable()) {
shortCircuitServer.getCacheWriter().delete(key);
return;
}
getClient().invoke(new DeleteOperation<K, V>((K)key));
}
@Override
public void deleteAll(Collection<?> keys) {
if (isDirectCallable()) {
shortCircuitServer.getCacheWriter().deleteAll(keys);
return;
}
getClient().invoke(new DeleteAllOperation<K, V>((Collection<K>) keys));
}
/**
* The {@link DeleteAllOperation} representing a {@link CacheWriter#deleteAll(java.util.Collection)}
* request.
*
* @param <K> the type of keys
* @param <V> the type of values
*/
private static class DeleteAllOperation<K, V> implements Operation<Map<K, V>> {
/**
* The keys to Delete.
*/
private Collection<? extends K> keys;
/**
* Constructs a {@link DeleteAllOperation}.
*
* @param keys the keys to Delete
*/
public DeleteAllOperation(Collection<? extends K> keys) {
this.keys = keys;
}
/**
* {@inheritDoc}
*/
@Override
public String getType() {
return "deleteAll";
}
/**
* {@inheritDoc}
*/
@Override
public Map<K, V> onInvoke(ObjectInputStream ois, ObjectOutputStream oos)
throws IOException, ClassNotFoundException, ExecutionException {
// send the keys to Delete
for (K key : keys) {
oos.writeObject(key);
}
oos.writeObject(null);
// check for remote exceptions
Object result = ois.readObject();
Collection<K> notDeletedKeys;
if (result instanceof RuntimeException) {
notDeletedKeys = (Collection<K>) ois.readObject();
// Partial Success processsing
// returned keys were not able to be deleted. remove from original keys list.
Iterator<? extends K> iter = keys.iterator();
while (iter.hasNext()) {
if (!notDeletedKeys.contains(iter.next())) {
iter.remove();
}
}
throw(RuntimeException) result;
} else {
// if no exception then all keys were deleted, remove all these keys to record that they were deleted.
keys.clear();
return null;
}
}
}
/**
* The {@link DeleteOperation} representing a {@link CacheWriter#delete(Object)}
* request.
*
* @param <K> the type of keys
* @param <V> the type of values
*/
private static class DeleteOperation<K, V> implements Operation<V> {
/**
* The key to Delete.
*/
private K key;
/**
* Constructs a {@link DeleteOperation}.
*
* @param key the Key to Delete
*/
public DeleteOperation(K key) {
this.key = key;
}
/**
* {@inheritDoc}
*/
@Override
public String getType() {
return "delete";
}
/**
* {@inheritDoc}
*/
@Override
public V onInvoke(ObjectInputStream ois, ObjectOutputStream oos) throws IOException, ClassNotFoundException {
oos.writeObject(key);
Object o = ois.readObject();
if (o instanceof RuntimeException) {
throw(RuntimeException) o;
} else {
return null;
}
}
}
/**
* The {@link WriteAllOperation} representing a {@link Cache#putAll(java.util.Map)} )}
* request.
*
* @param <K> the type of keys
* @param <V> the type of values
*/
private static class WriteAllOperation<K, V> implements Operation<Map<K, V>> {
/**
* The entries to write.
*/
private Collection<Cache.Entry<? extends K, ? extends V>> entries;
/**
* Constructs a {@link WriteAllOperation}.
*
* @param entries the entries to write
*/
public WriteAllOperation(Collection<Cache.Entry<? extends K, ? extends V>> entries) {
this.entries = entries;
}
/**
* {@inheritDoc}
*/
@Override
public String getType() {
return "writeAll";
}
/**
* {@inheritDoc}
*/
@Override
public Map<K, V> onInvoke(ObjectInputStream ois, ObjectOutputStream oos)
throws IOException, ClassNotFoundException {
// send the entries to write
for (Cache.Entry<? extends K, ? extends V> entry : entries) {
oos.writeObject(entry.getKey());
oos.writeObject(entry.getValue());
}
oos.writeObject(null);
Object o = ois.readObject();
if (o instanceof RuntimeException) {
// Partial Success processsing, read in keys that failed to be written
HashSet<K> failedToWriteKeys = new HashSet<>();
K key = (K) ois.readObject();
while (key != null) {
failedToWriteKeys.add(key);
key = (K) ois.readObject();
}
Iterator<Cache.Entry<? extends K, ? extends V>> iter = entries.iterator();
while (iter.hasNext()) {
if (!failedToWriteKeys.contains(iter.next().getKey())) {
iter.remove();
}
}
throw(RuntimeException) o;
} else {
entries.clear();
return null;
}
}
}
/**
* The {@link WriteOperation} representing a {@link CacheWriter#write(javax.cache.Cache.Entry)}
* request.
*
* @param <K> the type of keys
* @param <V> the type of values
*/
private static class WriteOperation<K, V> implements Operation<V> {
/**
* The key to load.
*/
private Cache.Entry<? extends K, ? extends V> entry;
/**
* Constructs a {@link WriteOperation}.
*
* @param entry the entry to write
*/
public WriteOperation(Cache.Entry<? extends K, ? extends V> entry) {
this.entry = entry;
}
/**
* {@inheritDoc}
*/
@Override
public String getType() {
return "write";
}
/**
* {@inheritDoc}
*/
@Override
public V onInvoke(ObjectInputStream ois, ObjectOutputStream oos) throws IOException, ClassNotFoundException {
oos.writeObject(entry.getKey());
oos.writeObject(entry.getValue());
Object o = ois.readObject();
if (o instanceof RuntimeException) {
throw(RuntimeException) o;
} else {
return null;
}
}
}
}