package org.infinispan.query.remote.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.management.MBeanException; import javax.management.ObjectName; import org.infinispan.Cache; import org.infinispan.commons.CacheException; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.global.GlobalConfiguration; import org.infinispan.factories.annotations.Inject; import org.infinispan.interceptors.locking.PessimisticLockingInterceptor; import org.infinispan.jmx.annotations.MBean; import org.infinispan.jmx.annotations.ManagedAttribute; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.jmx.annotations.Parameter; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.protostream.BaseMarshaller; import org.infinispan.protostream.DescriptorParserException; import org.infinispan.protostream.ProtobufUtil; import org.infinispan.protostream.SerializationContext; import org.infinispan.protostream.config.Configuration; import org.infinispan.query.remote.CompatibilityProtoStreamMarshaller; import org.infinispan.query.remote.ProtobufMetadataManager; import org.infinispan.query.remote.client.MarshallerRegistration; import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; import org.infinispan.query.remote.impl.indexing.IndexingMetadata; import org.infinispan.registry.InternalCacheRegistry; import org.infinispan.registry.InternalCacheRegistry.Flag; import org.infinispan.security.AuthorizationPermission; import org.infinispan.security.impl.CacheRoleImpl; import org.infinispan.transaction.LockingMode; import org.infinispan.transaction.TransactionMode; import org.infinispan.util.concurrent.IsolationLevel; /** * @author anistor@redhat.com * @since 7.0 */ @MBean(objectName = ProtobufMetadataManagerConstants.OBJECT_NAME, description = "Component that acts as a manager and container for Protocol Buffers message type definitions in the scope of a CacheManger.") public final class ProtobufMetadataManagerImpl implements ProtobufMetadataManager { private Cache<String, String> protobufSchemaCache; private ObjectName objectName; private final SerializationContext serCtx; private EmbeddedCacheManager cacheManager; public ProtobufMetadataManagerImpl() { Configuration.Builder cfgBuilder = Configuration.builder(); IndexingMetadata.configure(cfgBuilder); serCtx = ProtobufUtil.newSerializationContext(cfgBuilder.build()); try { MarshallerRegistration.registerMarshallers(serCtx); } catch (IOException | DescriptorParserException e) { throw new CacheException("Failed to initialise the Protobuf serialization context", e); } } @Inject protected void init(EmbeddedCacheManager cacheManager, InternalCacheRegistry internalCacheRegistry) { this.cacheManager = cacheManager; internalCacheRegistry.registerInternalCache(PROTOBUF_METADATA_CACHE_NAME, getProtobufMetadataCacheConfig().build(), EnumSet.of(Flag.USER, Flag.PROTECTED, Flag.PERSISTENT)); } /** * Obtain the cache, lazily. */ Cache<String, String> getCache() { if (protobufSchemaCache == null) { protobufSchemaCache = cacheManager.getCache(PROTOBUF_METADATA_CACHE_NAME); } return protobufSchemaCache; } private ConfigurationBuilder getProtobufMetadataCacheConfig() { GlobalConfiguration globalConfiguration = cacheManager.getGlobalComponentRegistry().getGlobalConfiguration(); CacheMode cacheMode = globalConfiguration.isClustered() ? CacheMode.REPL_SYNC : CacheMode.LOCAL; ConfigurationBuilder cfg = new ConfigurationBuilder(); cfg.transaction() .transactionMode(TransactionMode.TRANSACTIONAL).invocationBatching().enable() .transaction().lockingMode(LockingMode.PESSIMISTIC) .locking().isolationLevel(IsolationLevel.READ_COMMITTED).useLockStriping(false) .clustering().cacheMode(cacheMode).sync() .stateTransfer().fetchInMemoryState(true).awaitInitialTransfer(false) .compatibility().enable().marshaller(new CompatibilityProtoStreamMarshaller()) .customInterceptors().addInterceptor() .interceptor(new ProtobufMetadataManagerInterceptor()).after(PessimisticLockingInterceptor.class); if (globalConfiguration.security().authorization().enabled()) { globalConfiguration.security().authorization().roles().put(SCHEMA_MANAGER_ROLE, new CacheRoleImpl(SCHEMA_MANAGER_ROLE, AuthorizationPermission.ALL)); cfg.security().authorization().enable().role(SCHEMA_MANAGER_ROLE); } return cfg; } @Override public ObjectName getObjectName() { return objectName; } @Override public void setObjectName(ObjectName objectName) { this.objectName = objectName; } @Override public void registerMarshaller(BaseMarshaller<?> marshaller) { serCtx.registerMarshaller(marshaller); } @ManagedOperation(description = "Registers a Protobuf definition file", displayName = "Register a Protofile") @Override public void registerProtofile(@Parameter(name = "fileName", description = "the name of the .proto file") String fileName, @Parameter(name = "contents", description = "contents of the file") String contents) { getCache().put(fileName, contents); } @ManagedOperation(description = "Registers multiple Protobuf definition files", displayName = "Register Protofiles") @Override public void registerProtofiles(@Parameter(name = "fileNames", description = "names of the protofiles") String[] fileNames, @Parameter(name = "fileContents", description = "content of the files") String[] contents) throws Exception { if (fileNames.length != contents.length) { throw new MBeanException(new IllegalArgumentException("invalid parameter sizes")); } Map<String, String> files = new HashMap<>(fileNames.length); for (int i = 0; i < fileNames.length; i++) { files.put(fileNames[i], contents[i]); } getCache().putAll(files); } @ManagedOperation(description = "Unregisters a Protobuf definition files", displayName = "Unregister a Protofiles") @Override public void unregisterProtofile(@Parameter(name = "fileName", description = "the name of the .proto file") String fileName) { if (getCache().remove(fileName) == null) { throw new IllegalArgumentException("File does not exist : " + fileName); } } @ManagedOperation(description = "Unregisters multiple Protobuf definition files", displayName = "Unregister Protofiles") @Override public void unregisterProtofiles(@Parameter(name = "fileNames", description = "names of the protofiles") String[] fileNames) { for (String fileName : fileNames) { if (getCache().remove(fileName) == null) { throw new IllegalArgumentException("File does not exist : " + fileName); } } } @ManagedAttribute(description = "The names of all Protobuf files", displayName = "Protofile Names") @Override public String[] getProtofileNames() { List<String> fileNames = new ArrayList<>(); for (String k : getCache().keySet()) { if (k.endsWith(PROTO_KEY_SUFFIX)) { fileNames.add(k); } } Collections.sort(fileNames); return fileNames.toArray(new String[fileNames.size()]); } @ManagedOperation(description = "Get the contents of a protobuf definition file", displayName = "Get Protofile") @Override public String getProtofile(@Parameter(name = "fileName", description = "the name of the .proto file") String fileName) { if (!fileName.endsWith(PROTO_KEY_SUFFIX)) { throw new IllegalArgumentException("The file name must have \".proto\" suffix"); } String fileContents = getCache().get(fileName); if (fileContents == null) { throw new IllegalArgumentException("File does not exist : " + fileName); } return fileContents; } @ManagedAttribute(description = "The names of the files that have errors, if any", displayName = "Files With Errors") @Override public String[] getFilesWithErrors() { String filesWithErrors = getCache().get(ERRORS_KEY_SUFFIX); if (filesWithErrors == null) { return null; } String[] fileNames = filesWithErrors.split("\n"); Arrays.sort(fileNames); return fileNames; } @ManagedOperation(description = "Obtains the errors associated with a protobuf definition file", displayName = "Get Errors For A File") @Override public String getFileErrors(@Parameter(name = "fileName", description = "the name of the .proto file") String fileName) { if (!fileName.endsWith(PROTO_KEY_SUFFIX)) { throw new IllegalArgumentException("The file name must have \".proto\" suffix"); } if (!getCache().containsKey(fileName)) { throw new IllegalArgumentException("File does not exist : " + fileName); } return getCache().get(fileName + ERRORS_KEY_SUFFIX); } SerializationContext getSerializationContext() { return serCtx; } /** * Obtains the ProtobufMetadataManagerImpl instance associated to a cache manager. */ private static ProtobufMetadataManagerImpl getProtobufMetadataManager(EmbeddedCacheManager cacheManager) { if (cacheManager == null) { throw new IllegalArgumentException("cacheManager cannot be null"); } ProtobufMetadataManagerImpl metadataManager = (ProtobufMetadataManagerImpl) cacheManager.getGlobalComponentRegistry().getComponent(ProtobufMetadataManager.class); if (metadataManager == null) { throw new IllegalStateException("ProtobufMetadataManager not initialised yet!"); } return metadataManager; } public static SerializationContext getSerializationContextInternal(EmbeddedCacheManager cacheManager) { return getProtobufMetadataManager(cacheManager).getSerializationContext(); } }