/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.utils.cache; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apereo.portal.spring.beans.factory.ObjectMapperFactoryBean; import org.apereo.portal.utils.Populator; /** * A object designed for use as a cache key. It assumes that all key values are immutable and * pre-computes the hashCode. <br> * Tags can be added to a key which are used with the {@link TaggedCacheEntry} facilities. These * tags ARE NOT included in CacheKey comparison. * */ public final class CacheKey implements Serializable, TaggedCacheEntry { private static final long serialVersionUID = 1L; private static final ObjectWriter WRITER; static { ObjectMapper mapper; try { final ObjectMapperFactoryBean omfb = new ObjectMapperFactoryBean(); omfb.afterPropertiesSet(); mapper = omfb.getObject(); } catch (Exception e) { mapper = new ObjectMapper(); mapper.findAndRegisterModules(); } WRITER = mapper.writerWithDefaultPrettyPrinter(); } /** Utility for building more complex cache keys */ public static final class CacheKeyBuilder<K extends Serializable, V extends Serializable> implements Populator<K, V> { private final String source; private ArrayList<Serializable> keyList; private Map<Serializable, Serializable> keyMap; private Set<CacheEntryTag> tags; private CacheKeyBuilder(String source) { this.source = source; } public CacheKeyBuilder<K, V> addTag(CacheEntryTag t) { if (this.tags == null) { this.tags = new LinkedHashSet<CacheEntryTag>(); } this.tags.add(t); return this; } public CacheKeyBuilder<K, V> add(Serializable v) { checkKeyList(); this.keyList.add(v); return this; } public CacheKeyBuilder<K, V> addAll(Serializable... vs) { checkKeyList(); for (final Serializable v : vs) { this.keyList.add(v); } return this; } public CacheKeyBuilder<K, V> addAll(Collection<Serializable> vs) { checkKeyList(); for (final Serializable v : vs) { this.keyList.add(v); } return this; } public CacheKeyBuilder<K, V> put(K k, V v) { checkKeyMap(); this.keyMap.put(k, v); return this; } public CacheKeyBuilder<K, V> putAll(Properties p) { checkKeyMap(); for (final Map.Entry<Object, Object> ve : p.entrySet()) { this.keyMap.put((String) ve.getKey(), (String) ve.getValue()); } return this; } public CacheKeyBuilder<K, V> putAll(Map<? extends K, ? extends V> vm) { checkKeyMap(); for (final Map.Entry<? extends K, ? extends V> ve : vm.entrySet()) { this.keyMap.put(ve.getKey(), ve.getValue()); } return this; } public int size() { final int listLength = this.keyList != null ? this.keyList.size() : 0; final int mapLength = this.keyMap != null ? this.keyMap.size() : 0; return listLength + mapLength; } public CacheKey build() { final int listLength = this.keyList != null ? this.keyList.size() : 0; final int mapLength = this.keyMap != null ? this.keyMap.size() : 0; final int s = listLength + mapLength; final Serializable[] key = new Serializable[s]; if (listLength > 0) { this.keyList.toArray(key); } if (mapLength > 0) { int mapIndex = listLength; for (final Map.Entry<? extends Serializable, ? extends Serializable> ve : this.keyMap.entrySet()) { key[mapIndex++] = new Serializable[] {ve.getKey(), ve.getValue()}; } } return new CacheKey(this.source, key, this.tags); } private void checkKeyList() { if (this.keyList == null) { this.keyList = new ArrayList<Serializable>(); } } private void checkKeyMap() { if (this.keyMap == null) { this.keyMap = new LinkedHashMap<Serializable, Serializable>(); } } } public static <K extends Serializable, V extends Serializable> CacheKeyBuilder<K, V> builder( String source) { return new CacheKeyBuilder<K, V>(source); } public static CacheKey build(String source, Serializable... key) { return new CacheKey(source, key.clone(), null); } public static CacheKey buildTagged(String source, CacheEntryTag tag, Serializable... key) { return new CacheKey(source, key.clone(), ImmutableSet.of(tag)); } public static CacheKey build(String source, Collection<? extends Serializable> key) { return new CacheKey(source, key.toArray(new Serializable[key.size()]), null); } public static CacheKey build( String source, Map<? extends Serializable, ? extends Serializable> keyData) { final Serializable[] key = new Serializable[keyData.size()]; int mapIndex = 0; for (final Map.Entry<? extends Serializable, ? extends Serializable> ve : keyData.entrySet()) { key[mapIndex++] = new Serializable[] {ve.getKey(), ve.getValue()}; } return new CacheKey(source, key, null); } private final String source; private final Set<CacheEntryTag> tags; private final Serializable[] key; @JsonIgnore private final int hashCode; CacheKey(String source, Serializable[] key, Set<CacheEntryTag> tags) { this.source = source; this.key = key; if (tags == null) { this.tags = null; } else if (tags.size() == 1) { this.tags = Collections.singleton(tags.iterator().next()); } else { this.tags = ImmutableSet.copyOf(tags); } this.hashCode = this.internalHashCode(); } public Serializable getKey() { return this.key; } public String getSource() { return this.source; } @Override public Set<CacheEntryTag> getTags() { return this.tags; } private int internalHashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.deepHashCode(key); result = prime * result + ((source == null) ? 0 : source.hashCode()); return result; } @Override public int hashCode() { return this.hashCode; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; if (hashCode() != obj.hashCode()) return false; CacheKey other = (CacheKey) obj; if (!Arrays.deepEquals(key, other.key)) return false; if (source == null) { if (other.source != null) return false; } else if (!source.equals(other.source)) return false; return true; } @Override public String toString() { //Try to use a JSON formatter for generating the toString try { return WRITER.writeValueAsString(this); } catch (JsonGenerationException e) { //ignore } catch (JsonMappingException e) { //ignore } catch (IOException e) { //ignore } //Fall back on a simpler tostring return "CacheKey [source=" + source + ", key=" + Arrays.toString(key) + ", tags=" + tags + "]"; } }