All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.athenz.zts.ZTSClientCache Maven / Gradle / Ivy

There is a newer version: 1.10.62
Show newest version
/*
 * Copyright 2020 Verizon Media.
 *
 * 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.yahoo.athenz.zts;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.impl.copy.ReadWriteCopier;
import org.ehcache.spi.serialization.Serializer;
import org.ehcache.spi.serialization.SerializerException;
import org.ehcache.xml.XmlConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

/**
 * Provides optional caching for {@link ZTSClient}. 
* This class is designed to support multiple caches - but is currently only support a * cache from {domain+principal} to {@link RoleAccess} - see * {@link com.yahoo.athenz.zts.ZTSClient#getRoleAccess(java.lang.String, java.lang.String)}.

* * Ehcache XML config file links:
    *
  • Documentation: https://www.ehcache.org/documentation/3.8/xml.html *
  • XSD: http://www.ehcache.org/ehcache.xml *
  • Example: https://www.ehcache.org/documentation/3.0/examples.html#xml-with-107-extension *


* * To enable caching, set the system-property {@link #ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS} * to an ehcache config XML file like this example:
{@code
 *      
 *          
 *              10
 *              10000
 *          
 *      
 * }
*/ public class ZTSClientCache { public static final String ZTS_CLIENT_PROP_CACHE_CLASS = "athenz.zts.client.cache_class"; public static final String ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS = "athenz.zts.client.ehcache_xml_path"; // Note: caches might be null ! private Cache roleAccessCache; // ...more to come? private File ehcacheConfigXmlFile; private XmlConfiguration xmlConfiguration; private CacheManagerBuilder cacheManagerBuilder; private CacheManager cacheManager; private static final Logger LOG = LoggerFactory.getLogger(ZTSClientCache.class); /** * Get the singleton instance - which is usually a ZTSClientCache. * However, another class could be used (usually for tests) - by providing its class-name in the system-property {@link #ZTS_CLIENT_PROP_CACHE_CLASS} * @return the singleton instance */ public static ZTSClientCache getInstance() { return SingletonHolder.INSTANCE; } public Cache getRoleAccessCache() { return roleAccessCache; } /** The correct way to implement singleton: see https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java */ private static class SingletonHolder { public static final ZTSClientCache INSTANCE = createZTSClientCache(); } /** Construct a ZTSClientCache using the configured class */ private static ZTSClientCache createZTSClientCache() throws ExceptionInInitializerError { String ztsClientCacheClass = System.getProperty(ZTS_CLIENT_PROP_CACHE_CLASS, ZTSClientCache.class.getName()); try { return (ZTSClientCache) Class.forName(ztsClientCacheClass).getConstructor().newInstance(); } catch (Exception exception) { throw new ExceptionInInitializerError(exception); } } public ZTSClientCache() { // Find the config file. String ehcacheConfigXmlFileName = System.getProperty(ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS); if ((ehcacheConfigXmlFileName == null) || ehcacheConfigXmlFileName.isEmpty()) { LOG.info("ZTSClient cache is disabled: system-property \"{}\" is not set", ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS); return; } // Check if the config file exists. ehcacheConfigXmlFile = new File(ehcacheConfigXmlFileName); if (! ehcacheConfigXmlFile.isFile()) { LOG.info("ZTSClient cache is disabled: system-property \"{}\" doesn't reference a file", ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS); xmlConfiguration = null; return; } // Load the config file. LOG.info("ZTSClient cache is initializing (system-property \"{}\" references the file \"{}\")", ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS, ehcacheConfigXmlFile.getAbsoluteFile()); try { xmlConfiguration = new XmlConfiguration(ehcacheConfigXmlFile.toURI().toURL()); } catch (Exception exception) { LOG.error("ZTSClient cache is disabled: system-property \"{}\" references the file \"{}\" - which has errors: ", ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS, ehcacheConfigXmlFile.getAbsoluteFile(), exception); return; } // We have multiple caches, but we want to use a single CacheManager: // Phase 1: build a CacheManager, after preparing all cache-configurations into it's builder. cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder(); List phase2Executions = prepareAllCaches(); try { cacheManager = cacheManagerBuilder.build(true); } catch (Exception exception) { LOG.error("ZTSClient cache is disabled: system-property \"{}\" references the file \"{}\" - which has build errors: ", ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS, ehcacheConfigXmlFile.getAbsoluteFile(), exception); return; } // Phase 2: build actual caches, and assign them to members. for (Runnable phase2Execution : phase2Executions) { if (phase2Execution != null) { phase2Execution.run(); } } } /** Call {@link #prepareCache} per each cache */ private List prepareAllCaches() { return Arrays.asList( prepareCache("role-access", DomainAndPrincipal.class, RoleAccess.class, null, RoleAccessCopierAndSerializer.class, cache -> roleAccessCache = cache) // ...more to come? ); } /** * Prepare an ehcache CacheConfiguration from one <ehcache:cache> element in the config xml file. * Returns a function that should be executed after cacheManagerBuilder is fully initialized, and cacheManager is built. */ private Runnable prepareCache( String cacheName, Class keyClass, Class valueClass, Class> keyCopierAndSerializerClass, Class> valueCopierAndSerializerClass, Consumer> cacheIsReady) { // Get our cache's configuration from the config file, and apply the value-serializer. CacheConfiguration cacheConfiguration; try { CacheConfigurationBuilder configurationBuilder = xmlConfiguration.newCacheConfigurationBuilderFromTemplate(cacheName, keyClass, valueClass); if (configurationBuilder == null) { LOG.info("ZTSClient \"{}\" cache is disabled: system-property \"{}\" references the file \"{}\" - which has errors in the element.", cacheName, ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS, ehcacheConfigXmlFile.getAbsoluteFile(), cacheName); return null; } if (keyCopierAndSerializerClass != null) { configurationBuilder = configurationBuilder .withKeySerializer(keyCopierAndSerializerClass) .withKeyCopier(keyCopierAndSerializerClass); } if (valueCopierAndSerializerClass != null) { configurationBuilder = configurationBuilder .withValueSerializer(valueCopierAndSerializerClass) .withValueCopier(valueCopierAndSerializerClass); } cacheConfiguration = configurationBuilder.build(); } catch (Exception exception) { LOG.info("ZTSClient \"{}\" cache is disabled: system-property \"{}\" references the file \"{}\" - which has errors in the element: ", cacheName, ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS, ehcacheConfigXmlFile.getAbsoluteFile(), cacheName, exception); return null; } // Store this cache's configuration - so it is available to the to-be-built cacheManager. cacheManagerBuilder = cacheManagerBuilder.withCache(cacheName, cacheConfiguration); // This will be executed after cacheManagerBuilder is fully initialized, and cacheManager is built. return () -> { Cache cache = cacheManager.getCache(cacheName, keyClass, valueClass); if (cache == null) { LOG.info("ZTSClient \"{}\" cache is disabled: system-property \"{}\" references the file \"{}\" - which has errors in the element: unknown error", cacheName, ZTS_CLIENT_PROP_EHCACHE_XML_PATH_ROLE_ACCESS, ehcacheConfigXmlFile.getAbsoluteFile(), cacheName); } else { LOG.info("ZTSClient \"{}\" cache is enabled", cacheName); cacheIsReady.accept(cache); } }; } /** * This class holds a domain and a principal - and served as a cache-key. * This class relies on {@link AbstractMap.SimpleEntry} for equals, hashCode, and serialization. */ public static class DomainAndPrincipal extends AbstractMap.SimpleEntry { public DomainAndPrincipal(String domain, String principal) { super(domain, principal); } } /** A combination of both {@link ReadWriteCopier} and {@link Serializer} */ public static abstract class CopierAndSerializer extends ReadWriteCopier implements Serializer { } /** Allows ehcache to copy/serialize {@link RoleAccess} instances */ public static class RoleAccessCopierAndSerializer extends CopierAndSerializer { private static final TypeReference MAP_STRING_STRING = new TypeReference() { }; /** This constructor is required by {@link ReadWriteCopier} */ public RoleAccessCopierAndSerializer() { } /** * This constructor is required by {@link Serializer} * @param classLoader ignored */ public RoleAccessCopierAndSerializer(ClassLoader classLoader) { } /** To support off-heap */ @Override public ByteBuffer serialize(RoleAccess roleAccess) throws SerializerException { CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); // not thread-safe try { String json = OBJECT_MAPPER.writeValueAsString(roleAccess); return encoder.encode(CharBuffer.wrap(json)); } catch (Exception exception) { throw new SerializerException(exception); } } /** To support off-heap */ @Override public RoleAccess read(ByteBuffer binary) throws SerializerException { CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); // not thread-safe try { String json = decoder.decode(binary).toString(); return OBJECT_MAPPER.readValue(json, MAP_STRING_STRING); } catch (Exception exception) { throw new SerializerException(exception); } } /** Extra safe - clone on both read and write */ @Override public RoleAccess copy(RoleAccess roleAccess) { RoleAccess clone = new RoleAccess(); clone.setRoles(roleAccess.getRoles()); return clone; } @Override public boolean equals(RoleAccess roleAccess, ByteBuffer binary) throws SerializerException { return roleAccess.equals(read(binary)); } } // To be used for Serializer implementations: private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy