/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.util.configuration.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* A wrapper to {@link Properties}, to restrict the availability of values to only those which have a key
* beginning with a given masking string.
*
* Supported methods to enumerate the list of properties are:
* <ul>
* <li>propertyNames()</li>
* <li>keySet()</li>
* <li>keys()</li>
* </ul>
* Other methods including methods returning Entries and values are not supported
*
* @author Sanne Grinovero
* @author Emmanuel Bernard
*/
public class MaskedProperty extends Properties implements Serializable {
private static final long serialVersionUID = -593307257383085113L;
private static final Log log = LoggerFactory.make();
private final Properties masked;
private final Properties fallBack;
private final String radix;
private transient Set<Object> propertyNames;
/**
* Provides a view to the provided Properties hiding
* all keys not starting with some [mask.].
*
* @param propsToMask the Properties containing the values.
* @param mask a {@link java.lang.String} object.
*/
public MaskedProperty(Properties propsToMask, String mask) {
this( propsToMask, mask, null );
}
/**
* Provides a view to the provided Properties hiding all keys not starting with some [mask.].
* If no value is found then a value is returned from propsFallBack, without masking.
*
* @param propsToMask the properties to mask
* @param mask the mask applied to the properties
* @param propsFallBack a fall-back map of properties in case a value is not found in the main one
*/
public MaskedProperty(Properties propsToMask, String mask, Properties propsFallBack) {
if ( propsToMask == null || mask == null ) {
throw new java.lang.IllegalArgumentException();
}
this.masked = propsToMask;
this.radix = mask + ".";
this.fallBack = propsFallBack;
}
@Override
public String getProperty(String key) {
String compositeKey = radix + key;
String value = masked.getProperty( compositeKey );
if ( value != null ) {
log.tracef( "found a match for key: [%s] value: %s", compositeKey, value );
return value;
}
else if ( fallBack != null ) {
return fallBack.getProperty( key );
}
else {
return null;
}
}
/**
* Check if a given properties is set.
*
* @param key the property key
* @return {@code true} if the property is set, {@code false} otherwise
* @throws IllegalArgumentException if the key is not a String instance
*/
@Override
public synchronized boolean containsKey(Object key) {
if ( !( key instanceof String ) ) {
throw new IllegalArgumentException( "key must be a String" );
}
return getProperty( key.toString() ) != null;
}
@Override
public String getProperty(String key, String defaultValue) {
String val = getProperty( key );
return ( val == null ) ? defaultValue : val;
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void list(PrintStream out) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void list(PrintWriter out) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void load(InputStream inStream) throws IOException {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void loadFromXML(InputStream in) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public synchronized Enumeration<?> propertyNames() {
initPropertyNames();
return Collections.enumeration( propertyNames );
}
private synchronized void initPropertyNames() {
if ( propertyNames != null ) {
return;
}
Set<Object> maskedProperties = new TreeSet<Object>();
//we use keys to be safe and avoid CCE for non String key entries
Enumeration<?> maskedNames = masked.propertyNames();
while ( maskedNames.hasMoreElements() ) {
Object key = maskedNames.nextElement();
if ( String.class.isInstance( key ) ) {
String maskedProperty = (String) key;
if ( maskedProperty.startsWith( radix ) ) {
maskedProperties.add( maskedProperty.substring( radix.length(), maskedProperty.length() ) );
}
}
}
if ( fallBack != null ) {
Enumeration<?> fallBackNames = fallBack.propertyNames();
while ( fallBackNames.hasMoreElements() ) {
Object key = fallBackNames.nextElement();
if ( String.class.isInstance( key ) ) {
maskedProperties.add( key );
}
}
}
propertyNames = Collections.unmodifiableSet( maskedProperties );
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void save(OutputStream out, String comments) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Object setProperty(String key, String value) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void store(OutputStream out, String comments)
throws IOException {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void storeToXML(OutputStream os, String comment,
String encoding) throws IOException {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void storeToXML(OutputStream os, String comment)
throws IOException {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void clear() {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Object clone() {
throw new UnsupportedOperationException();
}
@Override
public synchronized boolean contains(Object value) {
initPropertyNames();
return propertyNames.contains( value );
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Enumeration<Object> elements() {
//TODO
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Set<java.util.Map.Entry<Object, Object>> entrySet() {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Object get(Object key) {
throw new UnsupportedOperationException();
}
@Override
public synchronized boolean isEmpty() {
initPropertyNames();
return propertyNames.isEmpty();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public synchronized Enumeration<Object> keys() {
initPropertyNames();
return Collections.enumeration( propertyNames );
}
@Override
public synchronized Set<Object> keySet() {
initPropertyNames();
return propertyNames;
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Object put(Object key, Object value) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public void putAll(Map<? extends Object, ? extends Object> t) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
protected void rehash() {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Object remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public synchronized int size() {
initPropertyNames();
return propertyNames.size();
}
@Override
public synchronized String toString() {
HashMap fake = new HashMap();
Enumeration<?> names = propertyNames();
while ( names.hasMoreElements() ) {
Object nextElement = names.nextElement();
fake.put( nextElement, this.getProperty( nextElement.toString() ) );
}
return fake.toString();
}
/**
* @throws UnsupportedOperationException always
*/
@Override
public Collection<Object> values() {
throw new UnsupportedOperationException();
}
@Override
public synchronized int hashCode() {
final int prime = 31;
int result = ( ( fallBack == null ) ? 0 : fallBack.hashCode() );
result = prime * result + masked.hashCode();
result = prime * result + radix.hashCode();
return result;
}
@Override
public synchronized boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
final MaskedProperty other = (MaskedProperty) obj;
if ( fallBack == null ) {
if ( other.fallBack != null ) {
return false;
}
}
else if ( !fallBack.equals( other.fallBack ) ) {
return false;
}
if ( !masked.equals( other.masked ) ) {
return false;
}
if ( !radix.equals( other.radix ) ) {
return false;
}
return true;
}
}