package org.jivesoftware.openfire.group; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.jivesoftware.database.DbConnectionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.jivesoftware.util.PersistableMap; import org.xmpp.packet.JID; /** * Shared base class for Openfire GroupProvider implementations. By default * all mutator methods throw {@link UnsupportedOperationException}. In * addition, group search operations are disabled. * * Subclasses may optionally implement these capabilities, and must also * at minimum implement the {@link GroupProvider#getGroup(String)} method. * * @author Tom Evans */ public abstract class AbstractGroupProvider implements GroupProvider { private static final Logger Log = LoggerFactory.getLogger(AbstractGroupProvider.class); private static final String GROUPLIST_CONTAINERS = "SELECT groupName from ofGroupProp " + "where name='sharedRoster.groupList' " + "AND propValue LIKE ?"; private static final String PUBLIC_GROUPS = "SELECT groupName from ofGroupProp " + "WHERE name='sharedRoster.showInRoster' " + "AND propValue='everybody'"; private static final String GROUPS_FOR_PROP = "SELECT groupName from ofGroupProp " + "WHERE name=? " + "AND propValue=?"; private static final String LOAD_SHARED_GROUPS = "SELECT groupName FROM ofGroupProp WHERE name='sharedRoster.showInRoster' " + "AND propValue IS NOT NULL AND propValue <> 'nobody'"; private static final String LOAD_PROPERTIES = "SELECT name, propValue FROM ofGroupProp WHERE groupName=?"; // Mutator methods disabled for read-only group providers /** * @throws UnsupportedOperationException */ @Override public void addMember(String groupName, JID user, boolean administrator) { throw new UnsupportedOperationException("Cannot add members to read-only groups"); } /** * @throws UnsupportedOperationException */ @Override public void updateMember(String groupName, JID user, boolean administrator) { throw new UnsupportedOperationException("Cannot update members for read-only groups"); } /** * @throws UnsupportedOperationException */ @Override public void deleteMember(String groupName, JID user) { throw new UnsupportedOperationException("Cannot remove members from read-only groups"); } /** * Always true for a read-only provider */ @Override public boolean isReadOnly() { return true; } /** * @throws UnsupportedOperationException */ @Override public Group createGroup(String name) throws GroupAlreadyExistsException { throw new UnsupportedOperationException("Cannot create groups via read-only provider"); } /** * @throws UnsupportedOperationException */ @Override public void deleteGroup(String name) { throw new UnsupportedOperationException("Cannot remove groups via read-only provider"); } /** * @throws UnsupportedOperationException */ @Override public void setName(String oldName, String newName) throws GroupAlreadyExistsException { throw new UnsupportedOperationException("Cannot modify read-only groups"); } /** * @throws UnsupportedOperationException */ @Override public void setDescription(String name, String description) throws GroupNotFoundException { throw new UnsupportedOperationException("Cannot modify read-only groups"); } // Search methods may be overridden by read-only group providers /** * Returns true if the provider supports group search capability. This implementation * always returns false. */ @Override public boolean isSearchSupported() { return false; } /** * Returns a collection of group search results. This implementation * returns an empty collection. */ @Override public Collection<String> search(String query) { return Collections.emptyList(); } /** * Returns a collection of group search results. This implementation * returns an empty collection. */ @Override public Collection<String> search(String query, int startIndex, int numResults) { return Collections.emptyList(); } // Shared group methods may be overridden by read-only group providers /** * Returns the name of the groups that are shared groups. * * @return the name of the groups that are shared groups. */ @Override public Collection<String> getSharedGroupNames() { Collection<String> groupNames = new HashSet<>(); Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(LOAD_SHARED_GROUPS); rs = pstmt.executeQuery(); while (rs.next()) { groupNames.add(rs.getString(1)); } } catch (SQLException sqle) { Log.error(sqle.getMessage(), sqle); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } return groupNames; } @Override public Collection<String> getSharedGroupNames(JID user) { Set<String> answer = new HashSet<>(); Collection<String> userGroups = getGroupNames(user); answer.addAll(userGroups); for (String userGroup : userGroups) { answer.addAll(getVisibleGroupNames(userGroup)); } answer.addAll(getPublicSharedGroupNames()); return answer; } @Override public Collection<String> getVisibleGroupNames(String userGroup) { Set<String> groupNames = new HashSet<>(); Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(GROUPLIST_CONTAINERS); pstmt.setString(1, "%" + userGroup + "%"); rs = pstmt.executeQuery(); while (rs.next()) { groupNames.add(rs.getString(1)); } } catch (SQLException sqle) { Log.error(sqle.getMessage(), sqle); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } return groupNames; } @Override public Collection<String> search(String key, String value) { Set<String> groupNames = new HashSet<>(); Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(GROUPS_FOR_PROP); pstmt.setString(1, key); pstmt.setString(2, value); rs = pstmt.executeQuery(); while (rs.next()) { groupNames.add(rs.getString(1)); } } catch (SQLException sqle) { Log.error(sqle.getMessage(), sqle); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } return groupNames; } @Override public Collection<String> getPublicSharedGroupNames() { Set<String> groupNames = new HashSet<>(); Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(PUBLIC_GROUPS); rs = pstmt.executeQuery(); while (rs.next()) { groupNames.add(rs.getString(1)); } } catch (SQLException sqle) { Log.error(sqle.getMessage(), sqle); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } return groupNames; } @Override public boolean isSharingSupported() { return true; } /** * Returns a custom {@link Map} that updates the database whenever * a property value is added, changed, or deleted. * * @param group The target group * @return The properties for the given group */ @Override public PersistableMap<String,String> loadProperties(Group group) { // custom map implementation persists group property changes // whenever one of the standard mutator methods are called String name = group.getName(); PersistableMap<String,String> result = new DefaultGroupPropertyMap<>(group); Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); pstmt = con.prepareStatement(LOAD_PROPERTIES); pstmt.setString(1, name); rs = pstmt.executeQuery(); while (rs.next()) { String key = rs.getString(1); String value = rs.getString(2); if (key != null) { if (value == null) { result.remove(key); Log.warn("Deleted null property " + key + " for group: " + name); } else { result.put(key, value, false); // skip persistence during load } } else { // should not happen, but ... Log.warn("Ignoring null property key for group: " + name); } } } catch (SQLException sqle) { Log.error(sqle.getMessage(), sqle); } finally { DbConnectionManager.closeConnection(rs, pstmt, con); } return result; } }