/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.nifi.distributed.cache.server.map; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Map; import javax.net.ssl.SSLContext; import org.apache.nifi.distributed.cache.server.AbstractCacheServer; import org.apache.nifi.distributed.cache.server.EvictionPolicy; import org.apache.nifi.remote.StandardVersionNegotiator; import org.apache.nifi.remote.VersionNegotiator; public class MapCacheServer extends AbstractCacheServer { private final MapCache cache; public MapCacheServer(final String identifier, final SSLContext sslContext, final int port, final int maxSize, final EvictionPolicy evictionPolicy, final File persistencePath) throws IOException { super(identifier, sslContext, port); final MapCache simpleCache = new SimpleMapCache(identifier, maxSize, evictionPolicy); if (persistencePath == null) { this.cache = simpleCache; } else { final PersistentMapCache persistentCache = new PersistentMapCache(identifier, persistencePath, simpleCache); persistentCache.restore(); this.cache = persistentCache; } } /** * Refer {@link org.apache.nifi.distributed.cache.protocol.ProtocolHandshake#initiateHandshake(InputStream, OutputStream, VersionNegotiator)} * for details of each version enhancements. */ protected StandardVersionNegotiator getVersionNegotiator() { return new StandardVersionNegotiator(2, 1); } @Override protected boolean listen(final InputStream in, final OutputStream out, final int version) throws IOException { final DataInputStream dis = new DataInputStream(in); final DataOutputStream dos = new DataOutputStream(out); final String action = dis.readUTF(); try { switch (action) { case "close": { return false; } case "putIfAbsent": { final byte[] key = readValue(dis); final byte[] value = readValue(dis); final MapPutResult putResult = cache.putIfAbsent(ByteBuffer.wrap(key), ByteBuffer.wrap(value)); dos.writeBoolean(putResult.isSuccessful()); break; } case "put": { final byte[] key = readValue(dis); final byte[] value = readValue(dis); cache.put(ByteBuffer.wrap(key), ByteBuffer.wrap(value)); dos.writeBoolean(true); break; } case "containsKey": { final byte[] key = readValue(dis); final boolean contains = cache.containsKey(ByteBuffer.wrap(key)); dos.writeBoolean(contains); break; } case "getAndPutIfAbsent": { final byte[] key = readValue(dis); final byte[] value = readValue(dis); final MapPutResult putResult = cache.putIfAbsent(ByteBuffer.wrap(key), ByteBuffer.wrap(value)); if (putResult.isSuccessful()) { // Put was successful. There was no old value to get. dos.writeInt(0); } else { // we didn't put. Write back the previous value final byte[] byteArray = putResult.getExisting().getValue().array(); dos.writeInt(byteArray.length); dos.write(byteArray); } break; } case "get": { final byte[] key = readValue(dis); final ByteBuffer existingValue = cache.get(ByteBuffer.wrap(key)); if (existingValue == null) { // there was no existing value. dos.writeInt(0); } else { // a value already existed. final byte[] byteArray = existingValue.array(); dos.writeInt(byteArray.length); dos.write(byteArray); } break; } case "remove": { final byte[] key = readValue(dis); final boolean removed = cache.remove(ByteBuffer.wrap(key)) != null; dos.writeBoolean(removed); break; } case "removeByPattern": { final String pattern = dis.readUTF(); final Map<ByteBuffer, ByteBuffer> removed = cache.removeByPattern(pattern); dos.writeLong(removed == null ? 0 : removed.size()); break; } case "fetch": { final byte[] key = readValue(dis); final MapCacheRecord existing = cache.fetch(ByteBuffer.wrap(key)); if (existing == null) { // there was no existing value. dos.writeLong(-1); dos.writeInt(0); } else { // a value already existed. dos.writeLong(existing.getRevision()); final byte[] byteArray = existing.getValue().array(); dos.writeInt(byteArray.length); dos.write(byteArray); } break; } case "replace": { final byte[] key = readValue(dis); final long revision = dis.readLong(); final byte[] value = readValue(dis); final MapPutResult result = cache.replace(new MapCacheRecord(ByteBuffer.wrap(key), ByteBuffer.wrap(value), revision)); dos.writeBoolean(result.isSuccessful()); break; } default: { throw new IOException("Illegal Request"); } } } finally { dos.flush(); } return true; } @Override public void stop() throws IOException { try { super.stop(); } finally { cache.shutdown(); } } @Override protected void finalize() throws Throwable { if (!stopped) { stop(); } } private byte[] readValue(final DataInputStream dis) throws IOException { final int numBytes = dis.readInt(); final byte[] buffer = new byte[numBytes]; dis.readFully(buffer); return buffer; } }