![JAR search and dependency download from the Maven repository](/logo.png)
org.jivesoftware.openfire.group.DefaultGroupPropertyMap Maven / Gradle / Ivy
The newest version!
package org.jivesoftware.openfire.group;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.event.GroupEventDispatcher;
import org.jivesoftware.util.PersistableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of a writable {@link Map} to manage group properties.
* Updates made to the elements in this map will also be applied to the database.
* Note this implementation assumes group property changes will be relatively
* infrequent and therefore does not try to optimize database I/O for performance.
* Each call to a {@link Map} mutator method (direct or indirect via {@link Iterator})
* will result in a corresponding synchronous update to the database.
*
* @param Property key
* @param Property value
*/
public class DefaultGroupPropertyMap extends PersistableMap {
private static final long serialVersionUID = 3128889631577167040L;
private static final Logger logger = LoggerFactory.getLogger(DefaultGroupPropertyMap.class);
// moved from {@link Group} as these are specific to the default provider
private static final String DELETE_PROPERTY =
"DELETE FROM ofGroupProp WHERE groupName=? AND name=?";
private static final String DELETE_ALL_PROPERTIES =
"DELETE FROM ofGroupProp WHERE groupName=?";
private static final String UPDATE_PROPERTY =
"UPDATE ofGroupProp SET propValue=? WHERE name=? AND groupName=?";
private static final String INSERT_PROPERTY =
"INSERT INTO ofGroupProp (groupName, name, propValue) VALUES (?, ?, ?)";
private Group group;
/**
* Group properties map constructor; requires associated {@link Group} instance
* @param group The group that owns these properties
*/
public DefaultGroupPropertyMap(Group group) {
this.group = group;
}
/**
* Custom method to put properties into the map, optionally without
* triggering persistence. This is used when the map is being
* initially loaded from the database.
*
* @param key The property name
* @param value The property value
* @param persist True if the changes should be persisted to the database
* @return The original value or null if the property did not exist
*/
@Override
public V put(K key, V value, boolean persist) {
V originalValue = super.put(key, value);
// we only support persistence for
if (persist && key instanceof String && value instanceof String) {
if (logger.isDebugEnabled())
logger.debug("Persisting group property [" + key + "]: " + value);
if (originalValue instanceof String) { // existing property
updateProperty((String)key, (String)value, (String)originalValue);
} else {
insertProperty((String)key, (String)value);
}
}
return originalValue;
}
@Override
public V put(K key, V value) {
if (value == null) { // treat null value as "remove"
return remove(key);
} else {
return put(key, value, true);
}
}
@Override
public V remove(Object key) {
V result = super.remove(key);
if (key instanceof String) {
deleteProperty((String)key);
}
return result;
}
@Override
public void clear() {
super.clear();
deleteAllProperties();
}
@Override
public Set keySet() {
// custom class needed here to handle key.remove()
return new PersistenceAwareKeySet(super.keySet());
}
@Override
public Collection values() {
// custom class needed here to suppress value.remove()
return Collections.unmodifiableCollection(super.values());
}
@Override
public Set> entrySet() {
// custom class needed here to handle entrySet mutators
return new PersistenceAwareEntrySet>(super.entrySet());
}
/**
* Persistence-aware {@link Set} for group property keys. This class returns
* a custom iterator that can handle property removal.
*/
private class PersistenceAwareKeySet extends AbstractSet {
private Set delegate;
/**
* Sole constructor; requires wrapped {@link Set} for delegation
* @param delegate A collection of keys from the map
*/
public PersistenceAwareKeySet(Set delegate) {
this.delegate = delegate;
}
@Override
public Iterator iterator() {
return new KeyIterator(delegate.iterator());
}
@Override
public int size() {
return delegate.size();
}
}
/**
* This iterator updates the database when a property key is removed.
*/
private class KeyIterator implements Iterator {
private Iterator delegate;
private K current;
/**
* Sole constructor; requires wrapped {@link Iterator} for delegation
* @param delegate An iterator for all the keys from the map
*/
public KeyIterator(Iterator delegate) {
this.delegate = delegate;
}
/**
* Delegated to corresponding method in the backing {@link Iterator}
*/
@Override
public boolean hasNext() {
return delegate.hasNext();
}
/**
* Delegated to corresponding method in the backing {@link Iterator}
*/
@Override
public K next() {
current = delegate.next();
return current;
}
/**
* Removes the property corresponding to the current key from
* the underlying map. Also applies update to the database.
*/
@Override
public void remove() {
delegate.remove();
if (current instanceof String) {
deleteProperty((String)current);
}
current = null;
}
}
/**
* Persistence-aware {@link Set} for group properties (as {@link Map.Entry})
*/
private class PersistenceAwareEntrySet implements Set> {
private Set> delegate;
/**
* Sole constructor; requires wrapped {@link Set} for delegation
* @param delegate A collection of entries ({@link Map.Entry}) from the map
*/
public PersistenceAwareEntrySet(Set> delegate) {
this.delegate = delegate;
}
/**
* Returns a custom iterator for the entries in the backing map
*/
@Override
public Iterator> iterator() {
return new EntryIterator>(delegate.iterator());
}
/**
* Removes the given key from the backing map, and applies the
* corresponding update to the database.
*
* @param o A {@link Map.Entry} within this set
* @return True if the set contained the given key
*/
@Override
public boolean remove(Object o) {
boolean propertyExists = delegate.remove(o);
if (propertyExists) {
deleteProperty((String)((Entry)o).getKey());
}
return propertyExists;
}
/**
* Removes all the elements in the set, and applies the
* corresponding update to the database.
*/
@Override
public void clear() {
delegate.clear();
deleteAllProperties();
}
// these methods are problematic (and not really necessary),
// so they are not implemented
/**
* @throws UnsupportedOperationException
*/
@Override
public boolean removeAll(Collection> c) {
throw new UnsupportedOperationException();
}
/**
* @throws UnsupportedOperationException
*/
@Override
public boolean retainAll(Collection> c) {
throw new UnsupportedOperationException();
}
// per docs for {@link Map.entrySet}, these methods are not supported
/**
* @throws UnsupportedOperationException
*/
@Override
public boolean add(Entry o) {
return delegate.add(o);
}
/**
* @throws UnsupportedOperationException
*/
@Override
public boolean addAll(Collection extends Entry> c) {
return delegate.addAll(c);
}
// remaining {@link Set} methods can be delegated safely
/**
* Delegated to corresponding method in the backing {@link Set}
*/
@Override
public int size() {
return delegate.size();
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
@Override
public Object[] toArray() {
return delegate.toArray();
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
@Override
public T[] toArray(T[] a) {
return delegate.toArray(a);
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
@Override
public boolean containsAll(Collection> c) {
return delegate.containsAll(c);
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
public boolean equals(Object o) {
return delegate.equals(o);
}
/**
* Delegated to corresponding method in the backing {@link Set}
*/
public int hashCode() {
return delegate.hashCode();
}
}
/**
* Remove group property from the database when the {@link Iterator.remove}
* method is invoked via the {@link Map.entrySet} set
*/
private class EntryIterator implements Iterator> {
private Iterator> delegate;
private EntryWrapper current;
/**
* Sole constructor; requires wrapped {@link Iterator} for delegation
* @param delegate An iterator for all the keys from the map
*/
public EntryIterator(Iterator> delegate) {
this.delegate = delegate;
}
/**
* Delegated to corresponding method in the backing {@link Iterator}
*/
@Override
public boolean hasNext() {
return delegate.hasNext();
}
/**
* Delegated to corresponding method in the backing {@link Iterator}
*/
@Override
public Entry next() {
current = new EntryWrapper<>(delegate.next());
return current;
}
/**
* Removes the property corresponding to the current key from
* the underlying map. Also applies update to the database.
*/
@Override
public void remove() {
delegate.remove();
K key = current.getKey();
if (key instanceof String) {
deleteProperty((String)key);
}
current = null;
}
}
/**
* Update the database when a group property is updated via {@link Map.Entry.setValue}
*/
private class EntryWrapper implements Entry {
private Entry delegate;
/**
* Sole constructor; requires wrapped {@link Map.Entry} for delegation
* @param delegate The corresponding entry from the map
*/
public EntryWrapper(Entry delegate) {
this.delegate = delegate;
}
/**
* Delegated to corresponding method in the backing {@link Map.Entry}
*/
@Override
public K getKey() {
return delegate.getKey();
}
/**
* Delegated to corresponding method in the backing {@link Map.Entry}
*/
@Override
public V getValue() {
return delegate.getValue();
}
/**
* Set the value of the property corresponding to this entry. This
* method also updates the database as needed depending on the new
* property value. A null value will cause the property to be deleted
* from the database.
*
* @param value The new property value
* @return The old value of the corresponding property
*/
@Override
public V setValue(V value) {
V oldValue = delegate.setValue(value);
K key = delegate.getKey();
if (key instanceof String) {
if (value instanceof String) {
if (oldValue == null) {
insertProperty((String) key, (String) value);
} else if (!value.equals(oldValue)) {
updateProperty((String)key,(String)value, (String)oldValue);
}
} else {
deleteProperty((String)key);
}
}
return oldValue;
}
}
/**
* Persist a new group property to the database for the current group
*
* @param key Property name
* @param value Property value
*/
private synchronized void insertProperty(String key, String value) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_PROPERTY);
pstmt.setString(1, group.getName());
pstmt.setString(2, key);
pstmt.setString(3, value);
pstmt.executeUpdate();
}
catch (SQLException e) {
logger.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
Map event = new HashMap<>();
event.put("propertyKey", key);
event.put("type", "propertyAdded");
GroupEventDispatcher.dispatchEvent(group,
GroupEventDispatcher.EventType.group_modified, event);
}
/**
* Update the value of an existing group property for the current group
*
* @param key Property name
* @param value Property value
* @param originalValue Original property value
*/
private synchronized void updateProperty(String key, String value, String originalValue) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_PROPERTY);
pstmt.setString(1, value);
pstmt.setString(2, key);
pstmt.setString(3, group.getName());
pstmt.executeUpdate();
}
catch (SQLException e) {
logger.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
Map event = new HashMap<>();
event.put("propertyKey", key);
event.put("type", "propertyModified");
event.put("originalValue", originalValue);
GroupEventDispatcher.dispatchEvent(group,
GroupEventDispatcher.EventType.group_modified, event);
}
/**
* Delete a group property from the database for the current group
*
* @param key Property name
*/
private synchronized void deleteProperty(String key) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_PROPERTY);
pstmt.setString(1, group.getName());
pstmt.setString(2, key);
pstmt.executeUpdate();
}
catch (SQLException e) {
logger.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
Map event = new HashMap<>();
event.put("type", "propertyDeleted");
event.put("propertyKey", key);
GroupEventDispatcher.dispatchEvent(group,
GroupEventDispatcher.EventType.group_modified, event);
}
/**
* Delete all properties from the database for the current group
*/
private synchronized void deleteAllProperties() {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_ALL_PROPERTIES);
pstmt.setString(1, group.getName());
pstmt.executeUpdate();
}
catch (SQLException e) {
logger.error(e.getMessage(), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
Map event = new HashMap<>();
event.put("type", "propertyDeleted");
event.put("propertyKey", "*");
GroupEventDispatcher.dispatchEvent(group,
GroupEventDispatcher.EventType.group_modified, event);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy