/* * Copyright (c) 2008-2017, Hazelcast, Inc. 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.hazelcast.ringbuffer.impl; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.config.RingbufferStoreConfig; import com.hazelcast.core.RingbufferStore; import com.hazelcast.core.RingbufferStoreFactory; import com.hazelcast.internal.diagnostics.Diagnostics; import com.hazelcast.internal.diagnostics.StoreLatencyPlugin; import com.hazelcast.internal.serialization.impl.HeapData; import com.hazelcast.nio.ClassLoaderUtil; import com.hazelcast.nio.serialization.Data; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.impl.NodeEngineImpl; import com.hazelcast.spi.serialization.SerializationService; import com.hazelcast.util.EmptyStatement; import java.util.Arrays; import static com.hazelcast.config.InMemoryFormat.BINARY; import static com.hazelcast.config.InMemoryFormat.NATIVE; import static com.hazelcast.util.Preconditions.checkNotNull; /** * Wrapper for the ring buffer store. In charge of creation of a ring buffer store from * configuration and enforcing rules defined by the ring buffer configuration (e.g. * store format) before forwarding the calls to the underlying ring buffer store. */ public final class RingbufferStoreWrapper implements RingbufferStore<Data> { private final String name; private boolean enabled; /** * Is the data being stored in serialized (BINARY/NATIVE) or deserialized (OBJECT) format. */ private InMemoryFormat inMemoryFormat; private RingbufferStore store; private SerializationService serializationService; private RingbufferStoreWrapper(String name) { this.name = name; } /** * Factory method that creates a {@link RingbufferStoreWrapper}. It attempts to create the ring buffer store from several * sources : * <ul> * <li>checks if the config contains the store implementation</li> * <li>tries to create the store from the ring buffer store class</li> * <li>tries to create one from the ring buffer store factory</li> * <li>tries to instantiate the factory from the factory class and create the store from the factory</li> * </ul> * * @param name ring buffer name * @param storeConfig store config of ring buffer * @param inMemoryFormat the format of the stored items (BINARY, OBJECT or NATIVE). NATIVE format translates into * binary values on the store method calls. * @param serializationService serialization service. * @return returns a new instance of {@link RingbufferStoreWrapper} */ public static RingbufferStoreWrapper create(String name, RingbufferStoreConfig storeConfig, InMemoryFormat inMemoryFormat, SerializationService serializationService, ClassLoader classLoader) { checkNotNull(name, "name should not be null"); checkNotNull(serializationService, "serializationService should not be null"); final RingbufferStoreWrapper storeWrapper = new RingbufferStoreWrapper(name); storeWrapper.serializationService = serializationService; if (storeConfig == null || !storeConfig.isEnabled()) { return storeWrapper; } // create ring buffer store. final RingbufferStore ringbufferStore = createRingbufferStore(name, storeConfig, classLoader); if (ringbufferStore != null) { storeWrapper.enabled = storeConfig.isEnabled(); storeWrapper.inMemoryFormat = inMemoryFormat; storeWrapper.store = ringbufferStore; } return storeWrapper; } private static RingbufferStore createRingbufferStore(String name, RingbufferStoreConfig storeConfig, ClassLoader classLoader) { // 1. Try to create store from `store impl.` class. RingbufferStore store = getRingbufferStore(storeConfig, classLoader); // 2. Try to create store from `store factory impl.` class. if (store == null) { store = getRingbufferStoreFactory(name, storeConfig, classLoader); } return store; } private static RingbufferStore getRingbufferStore(RingbufferStoreConfig storeConfig, ClassLoader classLoader) { if (storeConfig == null) { return null; } return getOrInstantiate(storeConfig.getStoreImplementation(), classLoader, storeConfig.getClassName()); } private static RingbufferStore getRingbufferStoreFactory(String name, RingbufferStoreConfig storeConfig, ClassLoader classLoader) { if (storeConfig == null) { return null; } final RingbufferStoreFactory implementation = storeConfig.getFactoryImplementation(); final String className = storeConfig.getFactoryClassName(); final RingbufferStoreFactory factory = getOrInstantiate(implementation, classLoader, className); return factory == null ? null : factory.newRingbufferStore(name, storeConfig.getProperties()); } private static <T> T getOrInstantiate(T instance, ClassLoader classLoader, String className) { if (instance != null) { return instance; } try { return ClassLoaderUtil.newInstance(classLoader, className); } catch (Exception ignored) { EmptyStatement.ignore(ignored); } return null; } /** * Is the ring buffer store enabled. * * @return if the store is enabled */ public boolean isEnabled() { return enabled; } void instrument(NodeEngine nodeEngine) { Diagnostics diagnostics = ((NodeEngineImpl) nodeEngine).getDiagnostics(); StoreLatencyPlugin storeLatencyPlugin = diagnostics.getPlugin(StoreLatencyPlugin.class); if (!enabled || storeLatencyPlugin == null) { return; } this.store = new LatencyTrackingRingbufferStore(store, storeLatencyPlugin, name); } @Override @SuppressWarnings("unchecked") public void store(long sequence, Data value) { final Object actualValue; if (isBinaryFormat()) { // WARNING: we can't pass original byte array to the user actualValue = Arrays.copyOf(value.toByteArray(), value.totalSize()); } else { // here we deserialize the object again (the first time is in the actual ring buffer). // if we are certain that the user cannot abuse the object, we can provide him with a reference to the // stored value actualValue = serializationService.toObject(value); } store.store(sequence, actualValue); } @Override @SuppressWarnings("unchecked") public void storeAll(long firstItemSequence, Data[] items) { final Object[] storedItems = new Object[items.length]; for (int i = 0; i < items.length; i++) { final Data value = items[i]; if (isBinaryFormat()) { // more defensive copying in the case of binary format storedItems[i] = Arrays.copyOf(value.toByteArray(), value.totalSize()); } else { // in the case of object format we have again deserialization. We could possibly pass the user the reference // to the item in the RB store if it is safe storedItems[i] = serializationService.toObject(value); } } store.storeAll(firstItemSequence, storedItems); } private boolean isBinaryFormat() { return inMemoryFormat.equals(BINARY) || inMemoryFormat.equals(NATIVE); } @Override public Data load(long sequence) { final Object val = store.load(sequence); if (val == null) { return null; } if (isBinaryFormat()) { byte[] dataBuffer = (byte[]) val; return new HeapData(Arrays.copyOf(dataBuffer, dataBuffer.length)); } return serializationService.toData(val); } @Override public long getLargestSequence() { return store.getLargestSequence(); } }