PrivateStorage.java 8.96 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision: 1759 $
 * $Date: 2005-08-09 19:32:51 -0300 (Tue, 09 Aug 2005) $
 *
6
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * 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.
19 20
 */

21
package org.jivesoftware.openfire;
22 23 24 25 26 27

import java.io.StringReader;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
28
import java.util.Map;
29 30 31
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

32 33 34 35 36 37 38 39 40 41 42 43 44
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.event.UserEventListener;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

45 46 47 48 49 50
/**
 * Private storage for user accounts (JEP-0049). It is used by some XMPP systems
 * for saving client settings on the server.
 *
 * @author Iain Shigeoka
 */
51
public class PrivateStorage extends BasicModule implements UserEventListener {
52

53 54
	private static final Logger Log = LoggerFactory.getLogger(PrivateStorage.class);

55
    private static final String LOAD_PRIVATE =
56
        "SELECT privateData FROM ofPrivate WHERE username=? AND namespace=?";
57
    private static final String INSERT_PRIVATE =
58
        "INSERT INTO ofPrivate (privateData,name,username,namespace) VALUES (?,?,?,?)";
59
    private static final String UPDATE_PRIVATE =
60
        "UPDATE ofPrivate SET privateData=?, name=? WHERE username=? AND namespace=?";
61
    private static final String DELETE_PRIVATES =
62
        "DELETE FROM ofPrivate WHERE username=?";
63

guus's avatar
guus committed
64 65
    private static final int POOL_SIZE = 10;
    
66 67 68
    // Currently no delete supported, we can detect an add of an empty element and
    // use that to signal a delete but that optimization doesn't seem necessary.
    // private static final String DELETE_PRIVATE =
69
    //     "DELETE FROM ofPrivate WHERE userID=? AND name=? AND namespace=?";
70 71 72 73 74 75

    private boolean enabled = JiveGlobals.getBooleanProperty("xmpp.privateStorageEnabled", true);

    /**
     * Pool of SAX Readers. SAXReader is not thread safe so we need to have a pool of readers.
     */
guus's avatar
guus committed
76
    private BlockingQueue<SAXReader> xmlReaders = new LinkedBlockingQueue<SAXReader>(POOL_SIZE);
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

    /**
     * Constructs a new PrivateStore instance.
     */
    public PrivateStorage() {
        super("Private user data storage");
    }

    /**
     * Returns true if private storage is enabled.
     *
     * @return true if private storage is enabled.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets whether private storage is enabled.
     *
     * @param enabled true if this private store is enabled.
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        JiveGlobals.setProperty("xmpp.privateStorageEnabled", Boolean.toString(enabled));
    }

    /**
     * Stores private data. If the name and namespace of the element matches another
     * stored private data XML document, then replace it with the new one.
     *
     * @param data the data to store (XML element)
     * @param username the username of the account where private data is being stored
     */
    public void add(String username, Element data) {
        if (enabled) {
113
            Connection con = null;
114
            PreparedStatement pstmt = null;
115
            ResultSet rs = null;
116 117 118 119 120 121 122
            try {
                StringWriter writer = new StringWriter();
                data.write(writer);
                con = DbConnectionManager.getConnection();
                pstmt = con.prepareStatement(LOAD_PRIVATE);
                pstmt.setString(1, username);
                pstmt.setString(2, data.getNamespaceURI());
123
                rs = pstmt.executeQuery();
124 125 126 127
                boolean update = false;
                if (rs.next()) {
                    update = true;
                }
128
                DbConnectionManager.fastcloseStmt(rs, pstmt);
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
                if (update) {
                    pstmt = con.prepareStatement(UPDATE_PRIVATE);
                }
                else {
                    pstmt = con.prepareStatement(INSERT_PRIVATE);
                }
                pstmt.setString(1, writer.toString());
                pstmt.setString(2, data.getName());
                pstmt.setString(3, username);
                pstmt.setString(4, data.getNamespaceURI());
                pstmt.executeUpdate();
            }
            catch (Exception e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
            finally {
145
                DbConnectionManager.closeConnection(rs, pstmt, con);
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
            }
        }
    }

    /**
     * Returns the data stored under a key corresponding to the name and namespace
     * of the given element. The Element must be in the form:<p>
     *
     * <code>&lt;name xmlns='namespace'/&gt;</code><p>
     *
     * If no data is currently stored under the given key, an empty element will be
     * returned.
     *
     * @param data an XML document who's element name and namespace is used to
     *      match previously stored private data.
     * @param username the username of the account where private data is being stored.
     * @return the data stored under the given key or the data element.
     */
    public Element get(String username, Element data) {
        if (enabled) {
            Connection con = null;
            PreparedStatement pstmt = null;
168
            ResultSet rs = null;
169 170 171 172 173 174 175 176
            SAXReader xmlReader = null;
            try {
                // Get a sax reader from the pool
                xmlReader = xmlReaders.take();
                con = DbConnectionManager.getConnection();
                pstmt = con.prepareStatement(LOAD_PRIVATE);
                pstmt.setString(1, username);
                pstmt.setString(2, data.getNamespaceURI());
177
                rs = pstmt.executeQuery();
178 179 180 181 182 183 184 185 186 187 188
                if (rs.next()) {
                    data.clearContent();
                    String result = rs.getString(1).trim();
                    Document doc = xmlReader.read(new StringReader(result));
                    data = doc.getRootElement();
                }
            }
            catch (Exception e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
            finally {
189
                DbConnectionManager.closeConnection(rs, pstmt, con);
190
                // Return the sax reader to the pool
191 192 193
                if (xmlReader != null) {
                    xmlReaders.add(xmlReader);
                }
194 195 196 197 198
            }
        }
        return data;
    }

199 200 201 202 203 204
    public void userCreated(User user, Map params) {
        //Do nothing
    }

    public void userDeleting(User user, Map params) {
        // Delete all private properties of the user
205
        Connection con = null;
206 207 208 209 210 211 212 213 214 215 216
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(DELETE_PRIVATES);
            pstmt.setString(1, user.getUsername());
            pstmt.executeUpdate();
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
        }
        finally {
217
            DbConnectionManager.closeConnection(pstmt, con);
218 219 220 221 222 223 224
        }
    }

    public void userModified(User user, Map params) {
        //Do nothing
    }

225 226
    @Override
	public void start() throws IllegalStateException {
227 228
        super.start();
        // Initialize the pool of sax readers
guus's avatar
guus committed
229
        for (int i=0; i<POOL_SIZE; i++) {
230 231 232
            SAXReader xmlReader = new SAXReader();
            xmlReader.setEncoding("UTF-8");
            xmlReaders.add(xmlReader);
233
        }
234 235 236
        // Add this module as a user event listener so we can delete
        // all user properties when a user is deleted
        UserEventDispatcher.addListener(this);
237 238
    }

239 240
    @Override
	public void stop() {
241 242 243
        super.stop();
        // Clean up the pool of sax readers
        xmlReaders.clear();
244 245
        // Remove this module as a user event listener
        UserEventDispatcher.removeListener(this);
246 247
    }
}