/*
* Copyright (C) 2006-2008 Jive Software. All rights reserved.
*
* 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 org.jivesoftware.openfire.plugin.spark;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.JiveID;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.util.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a Bookmark. Each bookmark can apply to a set of users and groups, or to
* everyone in the system. There are two types of bookmarks:<ul>
* <p/>
* <li>{@link Type#url url} -- a URL.
* <li>{@link Type#group_chat group chat} -- a group chat conference room.
* </ul>
* <p/>
* Each bookmark has a name and value. The value of the bokmark is either a URL or
* a conference room address, depending on the bookmark type. Each bookmark type has
* optional attributes stored as properties:<ul>
* <p/>
* <li><tt>autojoin</tt> ({@link Type#group_chat group chat} bookmarks): when set
* to <tt>true</tt>, the client is instructed to automatically join the
* conference room when starting up.</li>
* <li><tt>rss</tt> ({@link Type#url url} bookmarks): when set
* to <tt>true</tt>, indicates that the bookmark is for an RSS feed.</li>
* <li><tt>atom</tt> ({@link Type#url url} bookmarks): when set
* to <tt>true</tt>, indicates that the bookmark is for an ATOM feed.</li>
*
* @author Derek DeMoro, Matt Tucker
*/
@JiveID(55)
public class Bookmark {
private static final Logger Log = LoggerFactory.getLogger(Bookmark.class);
private static final String INSERT_BOOKMARK =
"INSERT INTO ofBookmark(bookmarkID, bookmarkType, bookmarkName, bookmarkValue, " +
"isGlobal) VALUES (?,?,?,?,?)";
private static final String INSERT_BOOKMARK_PERMISSIONS =
"INSERT INTO ofBookmarkPerm(bookmarkID, bookmarkType, name) VALUES(?,?,?)";
private static final String LOAD_BOOKMARK_PERMISSIONS =
"SELECT bookmarkType, name FROM ofBookmarkPerm WHERE bookmarkID=?";
// private static final String SAVE_BOOKMARK_PERMISSIONS =
// "UPDATE ofBookmarkPerm SET bookmarkType=?, name=? WHERE bookmarkID=?";
private static final String DELETE_BOOKMARK_PERMISSIONS =
"DELETE from ofBookmarkPerm WHERE bookmarkID=?";
private static final String SAVE_BOOKMARK =
"UPDATE ofBookmark SET bookmarkType=?, bookmarkName=?, bookmarkValue=?, isGlobal=? " +
"WHERE bookmarkID=?";
private static final String LOAD_BOOKMARK =
"SELECT bookmarkType, bookmarkName, bookmarkValue, isGlobal FROM " +
"ofBookmark WHERE bookmarkID=?";
private static final String LOAD_PROPERTIES =
"SELECT name, propValue FROM ofBookmarkProp WHERE bookmarkID=?";
private static final String INSERT_PROPERTY =
"INSERT INTO ofBookmarkProp (bookmarkID,name,propValue) VALUES (?,?,?)";
private static final String UPDATE_PROPERTY =
"UPDATE ofBookmarkProp SET propValue=? WHERE name=? AND bookmarkID=?";
private static final String DELETE_PROPERTY =
"DELETE FROM ofBookmarkProp WHERE bookmarkID=? AND name=?";
private long bookmarkID;
private Type type;
private String name;
private String value;
private boolean global;
private Collection<String> users;
private Collection<String> groups;
private Map<String, String> properties;
private static int USERS = 0;
private static int GROUPS = 1;
/**
* Creates a new bookmark.
*
* @param type the bookmark type.
* @param name the name of the bookmark.
* @param value the value of the bookmark.
*/
public Bookmark(Type type, String name, String value) {
this.type = type;
this.name = name;
this.value = value;
properties = new HashMap<String, String>();
try {
insertIntoDb();
insertBookmarkPermissions();
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
/**
* Loads an existing bookmark based on its ID.
*
* @param bookmarkID the bookmark ID.
* @throws NotFoundException if the bookmark does not exist or could not be loaded.
*/
public Bookmark(long bookmarkID) throws NotFoundException {
this.bookmarkID = bookmarkID;
loadFromDb();
loadPermissions();
}
/**
* Returns the unique ID of the bookmark.
*
* @return the bookmark ID.
*/
public long getBookmarkID() {
return bookmarkID;
}
/**
* Returns the {@link Type type} of the bookmark.
*
* @return the bookmark type.
*/
public Type getType() {
return type;
}
/**
* Sets the bookmark {@link Type type}.
*
* @param type the type of the bookmark.
*/
public void setType(Type type) {
this.type = type;
}
/**
* Returns the name of the bookmark; either the name of the URL or name of the
* conference room.
*
* @return the name of the bookmark.
*/
public String getName() {
return name;
}
/**
* Sets the name of the bookmark; either the name of the URL or name of the
* conference room.
*
* @param name the name of the bookmark.
*/
public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("Bookmark name must not be null.");
}
this.name = name;
saveToDb();
}
/**
* Returns the value of the bookmark; either a URL or a conference room address.
*
* @return the value of the bookmark.
*/
public String getValue() {
return value;
}
/**
* Sets the value of the bookmark; either a URL or a conference room address.
*
* @param value the value of the bookmark.
*/
public void setValue(String value) {
if (value == null) {
throw new IllegalArgumentException("Bookmark value must not be null.");
}
this.value = value;
saveToDb();
}
/**
* Returns the collection of usersnames that have been assigned the bookmark.
*
* @return the collection of usernames that have been assigned the bookmark.
*/
public Collection<String> getUsers() {
return users;
}
/**
* Sets the collection of usernames that have been assigned the bookmark.
*
* @param users the collection of usernames.
*/
public void setUsers(Collection<String> users) {
this.users = users;
saveToDb();
insertBookmarkPermissions();
}
/**
* Returns the collection of group names that have been assigned the the bookmark.
*
* @return a collection of group names.
*/
public Collection<String> getGroups() {
return groups;
}
public void setGroups(Collection<String> groups) {
this.groups = groups;
saveToDb();
insertBookmarkPermissions();
}
/**
* Returns true if this bookmark is applied to all users on the server. When true,
* the values for {@link #getGroups()} and {@link #getUsers()} have no effect.
*
* @return true if a global bookmark.
*/
public boolean isGlobalBookmark() {
return global;
}
/**
* Sets whether the bookmark is applied to all users on the server. When true,
* the values for {@link #getGroups()} and {@link #getUsers()} have no effect.
*
* @param global true if this is a global bookmark.
*/
public void setGlobalBookmark(boolean global) {
this.global = global;
saveToDb();
}
/**
* Returns an extended property. Each bookmark can have
* an arbitrary number of extended properties. This allows for enhanced
* functionality that is not part of the base interface.
*
* @param name the name of the property to get.
* @return the value of the property specified by <tt>name</tt>.
*/
public String getProperty(String name) {
if (properties == null) {
loadPropertiesFromDb();
}
return properties.get(name);
}
/**
* Return all immediate children property values of a parent property as an
* unmodifiable Collection of String values. A parent/child relationship is
* denoted by the "." character. For example, given the properties <tt>X.Y.A</tt>,
* <tt>X.Y.B</tt>, <tt>X.Y.C</tt> and <tt>X.Y.C.D</tt>, then the immediate child
* properties of <tt>X.Y</tt> are <tt>X.Y.A</tt>, <tt>X.Y.B</tt>, and <tt>X.Y.C</tt>
* (the value of <tt>X.Y.C.D</tt> would not be returned using this method).
*
* @param parentName the name of the parent property to return the children for.
* @return all Collection of all child property values for the given parent.
*/
public Collection<String> getProperties(String parentName) {
Object [] keys = properties.keySet().toArray();
ArrayList<String> results = new ArrayList<String>();
for (int i = 0, n = keys.length; i < n; i++) {
String key = (String)keys[i];
if (key.startsWith(parentName)) {
if (key.equals(parentName)) {
continue;
}
if (key.substring(parentName.length()).lastIndexOf(".") == 0) {
results.add(properties.get(key));
}
}
}
return Collections.unmodifiableCollection(results);
}
/**
* Sets an extended property. Each bookmark can have an
* arbitrary number of extended properties. This allows for enhanced
* functionality that is not part of the base interface.<p>
* <p/>
* If the property referenced by <code>name</code> already exists, its
* value will be updated.
*
* @param name the name of the property to set.
* @param value the new value for the property.
*/
public void setProperty(String name, String value) {
if (properties == null) {
loadPropertiesFromDb();
}
// See if we need to update a property value or insert a new one.
if (properties.containsKey(name)) {
// Only update the value in the database if the property value
// has changed.
if (!(value.equals(properties.get(name)))) {
properties.put(name, value);
updatePropertyInDb(name, value);
}
}
else {
properties.put(name, value);
insertPropertyIntoDb(name, value);
}
}
/**
* Deletes an extended property. If the property specified by
* <code>name</code> does not exist, this method will do nothing.
*
* @param name the name of the property to delete.
*/
public void deleteProperty(String name) {
if (properties == null) {
loadPropertiesFromDb();
}
// Only delete the property if it exists.
if (properties.containsKey(name)) {
properties.remove(name);
deletePropertyFromDb(name);
}
}
/**
* Returns an Iterator for the names of the extended properties.
*
* @return an Iterator for the names of the extended properties.
*/
public Iterator<String> getPropertyNames() {
if (properties == null) {
loadPropertiesFromDb();
}
return Collections.unmodifiableSet(properties.keySet()).iterator();
}
/**
* Tye type of the bookmark.
*/
@SuppressWarnings({"UnnecessarySemicolon"}) // Fix for QDox Source inspector
public enum Type {
/**
* A URL (typically HTTP).
*/
url,
/**
* A group chat conference room address.
*/
group_chat;
}
/**
* Inserts a new bookmark into the database.
*/
private void insertIntoDb() throws SQLException {
this.bookmarkID = SequenceManager.nextID(this);
Connection con = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
PreparedStatement pstmt = con.prepareStatement(INSERT_BOOKMARK);
pstmt.setLong(1, bookmarkID);
pstmt.setString(2, type.toString());
pstmt.setString(3, name);
pstmt.setString(4, value);
pstmt.setInt(5, global ? 1 : 0);
pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
}
finally {
DbConnectionManager.closeTransactionConnection(con, abortTransaction);
}
}
private void insertBookmarkPermissions() {
// Delete other permission.
try {
deleteBookmarkPermissions();
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
// Persist users
if (users != null) {
for (String user : users) {
try {
insertBookmarkPermission(USERS, user);
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
}
}
if (groups != null) {
for (String group : groups) {
try {
insertBookmarkPermission(GROUPS, group);
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
}
}
}
private void insertBookmarkPermission(int type, String name) throws SQLException {
Connection con = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
PreparedStatement pstmt = con.prepareStatement(INSERT_BOOKMARK_PERMISSIONS);
pstmt.setLong(1, bookmarkID);
pstmt.setInt(2, type);
pstmt.setString(3, name);
pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
}
finally {
DbConnectionManager.closeTransactionConnection(con, abortTransaction);
}
}
private void deleteBookmarkPermissions() throws SQLException {
Connection con = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
PreparedStatement pstmt = con.prepareStatement(DELETE_BOOKMARK_PERMISSIONS);
pstmt.setLong(1, bookmarkID);
pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
}
finally {
DbConnectionManager.closeTransactionConnection(con, abortTransaction);
}
}
private void loadPermissions() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<String> usersList = new ArrayList<String>();
List<String> groupList = new ArrayList<String>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_BOOKMARK_PERMISSIONS);
pstmt.setLong(1, bookmarkID);
rs = pstmt.executeQuery();
while (rs.next()) {
int type = rs.getInt(1);
String name = rs.getString(2);
if (type == USERS) {
usersList.add(name);
}
else {
groupList.add(name);
}
}
rs.close();
pstmt.close();
users = usersList;
groups = groupList;
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
/**
* Loads a bookmark from the database.
*
* @throws NotFoundException if the bookmark could not be loaded.
*/
private void loadFromDb() throws NotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_BOOKMARK);
pstmt.setLong(1, bookmarkID);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new NotFoundException("Bookmark not found: " + bookmarkID);
}
this.type = Type.valueOf(rs.getString(1));
this.name = rs.getString(2);
this.value = rs.getString(3);
this.global = rs.getInt(4) == 1;
rs.close();
pstmt.close();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
/**
* Saves a bookmark to the database.
*/
private void saveToDb() {
Connection con = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
PreparedStatement pstmt = con.prepareStatement(SAVE_BOOKMARK);
pstmt.setString(1, type.toString());
pstmt.setString(2, name);
pstmt.setString(3, value);
pstmt.setInt(4, global ? 1 : 0);
pstmt.setLong(5, bookmarkID);
pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException sqle) {
abortTransaction = true;
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeTransactionConnection(con, abortTransaction);
}
}
/**
* Loads properties from the database.
*/
private synchronized void loadPropertiesFromDb() {
this.properties = new Hashtable<String, String>();
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_PROPERTIES);
pstmt.setLong(1, bookmarkID);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String name = rs.getString(1);
String value = rs.getString(2);
properties.put(name, value);
}
rs.close();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/**
* Inserts a new property into the datatabase.
*/
private void insertPropertyIntoDb(String name, String value) {
Connection con = null;
PreparedStatement pstmt = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
pstmt = con.prepareStatement(INSERT_PROPERTY);
pstmt.setLong(1, bookmarkID);
pstmt.setString(2, name);
pstmt.setString(3, value);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
abortTransaction = true;
}
finally {
DbConnectionManager.closeTransactionConnection(pstmt, con, abortTransaction);
}
}
/**
* Updates a property value in the database.
*/
private void updatePropertyInDb(String name, String value) {
Connection con = null;
PreparedStatement pstmt = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
pstmt = con.prepareStatement(UPDATE_PROPERTY);
pstmt.setString(1, value);
pstmt.setString(2, name);
pstmt.setLong(3, bookmarkID);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
abortTransaction = true;
}
finally {
DbConnectionManager.closeTransactionConnection(pstmt, con, abortTransaction);
}
}
/**
* Deletes a property from the db.
*/
private synchronized void deletePropertyFromDb(String name) {
Connection con = null;
PreparedStatement pstmt = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
pstmt = con.prepareStatement(DELETE_PROPERTY);
pstmt.setLong(1, bookmarkID);
pstmt.setString(2, name);
pstmt.execute();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
abortTransaction = true;
}
finally {
DbConnectionManager.closeTransactionConnection(pstmt, con, abortTransaction);
}
}
}