JiveProperties.java 11.9 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
6
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
Matt Tucker's avatar
Matt Tucker committed
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.
Matt Tucker's avatar
Matt Tucker committed
19 20 21 22
 */

package org.jivesoftware.util;

23 24 25 26
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
27 28 29 30 31 32
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
Matt Tucker's avatar
Matt Tucker committed
33 34
import java.util.concurrent.ConcurrentHashMap;

35 36 37 38 39
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Matt Tucker's avatar
Matt Tucker committed
40 41 42 43 44
/**
 * Retrieves and stores Jive properties. Properties are stored in the database.
 *
 * @author Matt Tucker
 */
45
public class JiveProperties implements Map<String, String> {
Matt Tucker's avatar
Matt Tucker committed
46

47 48
	private static final Logger Log = LoggerFactory.getLogger(JiveProperties.class);

49 50 51 52
    private static final String LOAD_PROPERTIES = "SELECT name, propValue FROM ofProperty";
    private static final String INSERT_PROPERTY = "INSERT INTO ofProperty(name, propValue) VALUES(?,?)";
    private static final String UPDATE_PROPERTY = "UPDATE ofProperty SET propValue=? WHERE name=?";
    private static final String DELETE_PROPERTY = "DELETE FROM ofProperty WHERE name LIKE ?";
Matt Tucker's avatar
Matt Tucker committed
53

54
    private static JiveProperties instance = null;
Matt Tucker's avatar
Matt Tucker committed
55 56 57 58 59 60 61 62

    private Map<String, String> properties;

    /**
     * Returns a singleton instance of JiveProperties.
     *
     * @return an instance of JiveProperties.
     */
63 64 65 66 67 68 69
    public synchronized static JiveProperties getInstance() {
    	if (instance == null) {
    		JiveProperties props = new JiveProperties();
    		props.init();
    		instance = props;
    	}
        return instance;
Matt Tucker's avatar
Matt Tucker committed
70
    }
71
    private JiveProperties() { }
Matt Tucker's avatar
Matt Tucker committed
72 73 74

    /**
     * For internal use only. This method allows for the reloading of all properties from the
75
     * values in the database. This is required since it's quite possible during the setup
Matt Tucker's avatar
Matt Tucker committed
76 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
     * process that a database connection will not be available till after this class is
     * initialized. Thus, if there are existing properties in the database we will want to reload
     * this class after the setup process has been completed.
     */
    public void init() {
        if (properties == null) {
            properties = new ConcurrentHashMap<String, String>();
        }
        else {
            properties.clear();
        }

        loadProperties();
    }

    public int size() {
        return properties.size();
    }

    public void clear() {
        throw new UnsupportedOperationException();
    }

    public boolean isEmpty() {
        return properties.isEmpty();
    }

    public boolean containsKey(Object key) {
        return properties.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return properties.containsValue(value);
    }

111
    public Collection<String> values() {
Matt Tucker's avatar
Matt Tucker committed
112 113 114
        return Collections.unmodifiableCollection(properties.values());
    }

115 116
    public void putAll(Map<? extends String, ? extends String> t) {
        for (Map.Entry<? extends String, ? extends String> entry : t.entrySet() ) {
Matt Tucker's avatar
Matt Tucker committed
117 118 119 120
            put(entry.getKey(), entry.getValue());
        }
    }

121
    public Set<Map.Entry<String, String>> entrySet() {
Matt Tucker's avatar
Matt Tucker committed
122 123 124
        return Collections.unmodifiableSet(properties.entrySet());
    }

125
    public Set<String> keySet() {
Matt Tucker's avatar
Matt Tucker committed
126 127 128
        return Collections.unmodifiableSet(properties.keySet());
    }

129
    public String get(Object key) {
Matt Tucker's avatar
Matt Tucker committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
        return properties.get(key);
    }

    /**
     * Return all children property names of a parent property as a Collection
     * of String objects. For example, given the properties <tt>X.Y.A</tt>,
     * <tt>X.Y.B</tt>, and <tt>X.Y.C</tt>, then the 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 method
     * is not recursive; ie, it does not return children of children.
     *
     * @param parentKey the name of the parent property.
     * @return all child property names for the given parent.
     */
    public Collection<String> getChildrenNames(String parentKey) {
        Collection<String> results = new HashSet<String>();
        for (String key : properties.keySet()) {
            if (key.startsWith(parentKey + ".")) {
                if (key.equals(parentKey)) {
                    continue;
                }
                int dotIndex = key.indexOf(".", parentKey.length()+1);
                if (dotIndex < 1) {
                    if (!results.contains(key)) {
                        results.add(key);
                    }
                }
                else {
                    String name = parentKey + key.substring(parentKey.length(), dotIndex);
                    results.add(name);
                }
            }
        }
        return results;
    }

    /**
     * Returns all property names as a Collection of String values.
     *
     * @return all property names.
     */
    public Collection<String> getPropertyNames() {
        return properties.keySet();
    }

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    public String remove(Object key) {
        String value;
        synchronized (this) {
            value = properties.remove(key);
            // Also remove any children.
            Collection<String> propNames = getPropertyNames();
            for (String name : propNames) {
                if (name.startsWith((String)key)) {
                    properties.remove(name);
                }
            }
            deleteProperty((String)key);
        }

        // Generate event.
        Map<String, Object> params = Collections.emptyMap();
        PropertyEventDispatcher.dispatchEvent((String)key, PropertyEventDispatcher.EventType.property_deleted, params);

        // Send update to other cluster members.
193
        CacheFactory.doClusterTask(PropertyClusterEventTask.createDeleteTask((String) key));
194 195 196 197 198 199

        return value;
    }

    void localRemove(String key) {
        properties.remove(key);
Matt Tucker's avatar
Matt Tucker committed
200
        // Also remove any children.
201 202
        Collection<String> propNames = getPropertyNames();
        for (String name : propNames) {
203
            if (name.startsWith(key)) {
Matt Tucker's avatar
Matt Tucker committed
204 205 206
                properties.remove(name);
            }
        }
207 208

        // Generate event.
209
        Map<String, Object> params = Collections.emptyMap();
210
        PropertyEventDispatcher.dispatchEvent(key, PropertyEventDispatcher.EventType.property_deleted, params);
Matt Tucker's avatar
Matt Tucker committed
211 212
    }

213
    public String put(String key, String value) {
214 215 216 217 218 219
        if (value == null) {
            // This is the same as deleting, so remove it.
            return remove(key);
        }
        if (key == null) {
            throw new NullPointerException("Key cannot be null. Key=" +
Matt Tucker's avatar
Matt Tucker committed
220 221
                    key + ", value=" + value);
        }
222 223
        if (key.endsWith(".")) {
            key = key.substring(0, key.length()-1);
Matt Tucker's avatar
Matt Tucker committed
224
        }
225
        key = key.trim();
226 227 228 229 230 231 232 233 234
        String result;
        synchronized (this) {
            if (properties.containsKey(key)) {
                if (!properties.get(key).equals(value)) {
                    updateProperty(key, value);
                }
            }
            else {
                insertProperty(key, value);
Matt Tucker's avatar
Matt Tucker committed
235
            }
236

237 238
            result = properties.put(key, value);
        }
239

240
        // Generate event.
241
        Map<String, Object> params = new HashMap<String, Object>();
242
        params.put("value", value);
243
        PropertyEventDispatcher.dispatchEvent(key, PropertyEventDispatcher.EventType.property_set, params);
244

245 246 247
        // Send update to other cluster members.
        CacheFactory.doClusterTask(PropertyClusterEventTask.createPutTask(key, value));

248
        return result;
Matt Tucker's avatar
Matt Tucker committed
249 250
    }

251 252 253 254 255 256 257 258 259
    void localPut(String key, String value) {
        properties.put(key, value);

        // Generate event.
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("value", value);
        PropertyEventDispatcher.dispatchEvent(key, PropertyEventDispatcher.EventType.property_set, params);
    }

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
    public String getProperty(String name, String defaultValue) {
        String value = properties.get(name);
        if (value != null) {
            return value;
        }
        else {
            return defaultValue;
        }
    }

    public boolean getBooleanProperty(String name) {
        return Boolean.valueOf(get(name));
    }

    public boolean getBooleanProperty(String name, boolean defaultValue) {
        String value = get(name);
        if (value != null) {
            return Boolean.valueOf(value);
        }
        else {
            return defaultValue;
        }
    }

Matt Tucker's avatar
Matt Tucker committed
284
    private void insertProperty(String name, String value) {
285
    	Encryptor encryptor = getEncryptor();
Matt Tucker's avatar
Matt Tucker committed
286 287 288 289 290 291
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(INSERT_PROPERTY);
            pstmt.setString(1, name);
292
            pstmt.setString(2, JiveGlobals.isPropertyEncrypted(name) ? encryptor.encrypt(value) : value);
Derek DeMoro's avatar
Derek DeMoro committed
293
            pstmt.executeUpdate();
Matt Tucker's avatar
Matt Tucker committed
294 295
        }
        catch (SQLException e) {
296
            Log.error(e.getMessage(), e);
Matt Tucker's avatar
Matt Tucker committed
297 298
        }
        finally {
299
            DbConnectionManager.closeConnection(pstmt, con);
Matt Tucker's avatar
Matt Tucker committed
300 301 302 303
        }
    }

    private void updateProperty(String name, String value) {
304
    	Encryptor encryptor = getEncryptor();
Matt Tucker's avatar
Matt Tucker committed
305 306 307 308 309
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(UPDATE_PROPERTY);
310
            pstmt.setString(1, JiveGlobals.isPropertyEncrypted(name) ? encryptor.encrypt(value) : value);
Matt Tucker's avatar
Matt Tucker committed
311
            pstmt.setString(2, name);
Derek DeMoro's avatar
Derek DeMoro committed
312
            pstmt.executeUpdate();
Matt Tucker's avatar
Matt Tucker committed
313 314
        }
        catch (SQLException e) {
315
            Log.error(e.getMessage(), e);
Matt Tucker's avatar
Matt Tucker committed
316 317
        }
        finally {
318
            DbConnectionManager.closeConnection(pstmt, con);
Matt Tucker's avatar
Matt Tucker committed
319 320 321 322 323 324 325 326 327 328
        }
    }

    private void deleteProperty(String name) {
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(DELETE_PROPERTY);
            pstmt.setString(1, name + "%");
Derek DeMoro's avatar
Derek DeMoro committed
329
            pstmt.executeUpdate();
Matt Tucker's avatar
Matt Tucker committed
330 331
        }
        catch (SQLException e) {
332
            Log.error(e.getMessage(), e);
Matt Tucker's avatar
Matt Tucker committed
333 334
        }
        finally {
335
            DbConnectionManager.closeConnection(pstmt, con);
Matt Tucker's avatar
Matt Tucker committed
336 337 338 339
        }
    }

    private void loadProperties() {
340
    	Encryptor encryptor = getEncryptor();
Matt Tucker's avatar
Matt Tucker committed
341 342
        Connection con = null;
        PreparedStatement pstmt = null;
343
        ResultSet rs = null;
Matt Tucker's avatar
Matt Tucker committed
344 345 346
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(LOAD_PROPERTIES);
347
            rs = pstmt.executeQuery();
Matt Tucker's avatar
Matt Tucker committed
348 349 350
            while (rs.next()) {
                String name = rs.getString(1);
                String value = rs.getString(2);
351 352 353 354 355 356 357 358 359 360 361
                if (JiveGlobals.isPropertyEncrypted(name)) {
                	try { 
                		value = encryptor.decrypt(value); 
                	} catch (Exception ex) {
                    	Log.error("Failed to load encrypted property value for " + name, ex);
                    	value = null;
                	}
                }
                if (value != null) { 
                	properties.put(name, value); 
                }
Matt Tucker's avatar
Matt Tucker committed
362 363 364
            }
        }
        catch (Exception e) {
365
            Log.error(e.getMessage(), e);
Matt Tucker's avatar
Matt Tucker committed
366 367
        }
        finally {
368
            DbConnectionManager.closeConnection(rs, pstmt, con);
Matt Tucker's avatar
Matt Tucker committed
369 370
        }
    }
371 372 373 374
    
    private Encryptor getEncryptor() {
    	return JiveGlobals.getPropertyEncryptor();
    }
Matt Tucker's avatar
Matt Tucker committed
375
}