001: //=============================================================================
002: //=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
003: //=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
004: //=== and United Nations Environment Programme (UNEP)
005: //===
006: //=== This program is free software; you can redistribute it and/or modify
007: //=== it under the terms of the GNU General Public License as published by
008: //=== the Free Software Foundation; either version 2 of the License, or (at
009: //=== your option) any later version.
010: //===
011: //=== This program is distributed in the hope that it will be useful, but
012: //=== WITHOUT ANY WARRANTY; without even the implied warranty of
013: //=== MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: //=== General Public License for more details.
015: //===
016: //=== You should have received a copy of the GNU General Public License
017: //=== along with this program; if not, write to the Free Software
018: //=== Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
019: //===
020: //=== Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
021: //=== Rome - Italy. email: geonetwork@osgeo.org
022: //==============================================================================
023:
024: package org.fao.geonet.kernel.setting;
025:
026: import java.sql.SQLException;
027: import java.util.ArrayList;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.StringTokenizer;
033: import java.util.Vector;
034: import java.util.concurrent.locks.ReentrantReadWriteLock;
035: import jeeves.resources.dbms.Dbms;
036: import jeeves.server.resources.ProviderManager;
037: import jeeves.server.resources.ResourceListener;
038: import jeeves.server.resources.ResourceProvider;
039: import org.fao.geonet.constants.Geonet;
040: import org.jdom.Element;
041:
042: //=============================================================================
043:
044: /** Allows hierarchical management of application settings. The settings API
045: * has been designed with the following goals:
046: *
047: * - speed: all the settings tree is kept into memory
048: *
049: * - transactional: changes follow the rules of transactions. The only issue
050: * is that changes are not visible until commit. If a thread
051: * changes a value and then reads it, the thread gets the old
052: * value. Added settings will not be visible and removed ones
053: * will still be visible until commit.
054: *
055: * - concurrent: many thread can access the settings API at the same time. A
056: * read/write lock is used to arbitrate threads
057: *
058: * Multiple removes: there are no issues. If thread A removes a subtree S1 and
059: * another thread B removes a subtree S2 inside S1, the first thread to commit
060: * succeeds while the second always rises a 'cannot serializable exception'.
061: * In any commit combination, the settings integrity is maintained.
062: *
063: * Tree structure:
064: *
065: * + system
066: * | + options
067: * | + useProxy
068: * | + host
069: * | + port
070: * |
071: * + harvesting
072: */
073:
074: public class SettingManager {
075: private Setting root;
076:
077: //---------------------------------------------------------------------------
078: //---
079: //--- Constructor
080: //---
081: //---------------------------------------------------------------------------
082:
083: public SettingManager(Dbms dbms, ProviderManager provMan)
084: throws SQLException {
085: List list = dbms.select("SELECT * FROM Settings").getChildren();
086:
087: root = new Setting(0, null, null);
088: createSubTree(root, list);
089:
090: for (ResourceProvider rp : provMan.getProviders())
091: if (rp.getName().equals(Geonet.Res.MAIN_DB))
092: rp.addListener(resList);
093: }
094:
095: //---------------------------------------------------------------------------
096: //---
097: //--- API methods
098: //---
099: //---------------------------------------------------------------------------
100:
101: //---------------------------------------------------------------------------
102: //--- Getters
103: //---------------------------------------------------------------------------
104:
105: public Element get(String path, int level) {
106: lock.readLock().lock();
107:
108: try {
109: Setting s = resolve(path);
110:
111: return (s == null) ? null : build(s, level);
112: } finally {
113: lock.readLock().unlock();
114: }
115: }
116:
117: //---------------------------------------------------------------------------
118:
119: public String getValue(String path) {
120: lock.readLock().lock();
121:
122: try {
123: Setting s = resolve(path);
124:
125: return (s == null) ? null : s.getValue();
126: } finally {
127: lock.readLock().unlock();
128: }
129: }
130:
131: //---------------------------------------------------------------------------
132: //--- Setters
133: //---------------------------------------------------------------------------
134:
135: public boolean setName(Dbms dbms, String path, String name)
136: throws SQLException {
137: if (path == null)
138: throw new IllegalArgumentException("Path cannot be null");
139:
140: lock.writeLock().lock();
141:
142: try {
143: Setting s = resolve(path);
144:
145: if (s == null)
146: return false;
147:
148: dbms.execute("UPDATE Settings SET name=? WHERE id=?", name,
149: s.getId());
150: tasks.add(Task.getNameChangedTask(dbms, s, name));
151:
152: return true;
153: } finally {
154: lock.writeLock().unlock();
155: }
156: }
157:
158: //---------------------------------------------------------------------------
159:
160: public boolean setValue(Dbms dbms, String path, Object value)
161: throws SQLException {
162: Map<String, Object> values = new HashMap<String, Object>();
163: values.put(path, value);
164:
165: return setValues(dbms, values);
166: }
167:
168: //---------------------------------------------------------------------------
169:
170: public boolean setValues(Dbms dbms, Map<String, Object> values)
171: throws SQLException {
172: lock.writeLock().lock();
173:
174: try {
175: boolean success = true;
176:
177: for (Map.Entry<String, Object> entry : values.entrySet()) {
178: String path = entry.getKey();
179: String value = makeString(entry.getValue());
180:
181: Setting s = resolve(path);
182:
183: if (s == null)
184: success = false;
185: else {
186: dbms.execute(
187: "UPDATE Settings SET value=? WHERE id=?",
188: value, s.getId());
189: tasks.add(Task.getValueChangedTask(dbms, s, value));
190: }
191: }
192:
193: return success;
194: } finally {
195: lock.writeLock().unlock();
196: }
197: }
198:
199: //---------------------------------------------------------------------------
200: /** When adding to a newly created node, path must be 'id:...'
201: */
202:
203: public String add(Dbms dbms, String path, Object name, Object value)
204: throws SQLException {
205: if (name == null)
206: throw new IllegalArgumentException("Name cannot be null");
207:
208: String sName = makeString(name);
209: String sValue = makeString(value);
210:
211: lock.writeLock().lock();
212:
213: try {
214: //--- first, we look into the tasks list because the 'id' could have been
215: //--- added just now
216:
217: Setting parent = findAmongAdded(dbms, path);
218:
219: //--- if we fail, just do a normal search
220:
221: if (parent == null)
222: parent = resolve(path);
223:
224: if (parent == null)
225: return null;
226:
227: Setting child = new Setting(getNextSerial(dbms), sName,
228: sValue);
229:
230: String query = "INSERT INTO Settings(id, parentId, name, value) VALUES(?, ?, ?, ?)";
231:
232: dbms.execute(query, child.getId(), parent.getId(), sName,
233: sValue);
234:
235: tasks.add(Task.getAddedTask(dbms, parent, child));
236:
237: return Integer.toString(child.getId());
238: } finally {
239: lock.writeLock().unlock();
240: }
241: }
242:
243: //---------------------------------------------------------------------------
244:
245: public boolean remove(Dbms dbms, String path) throws SQLException {
246: lock.writeLock().lock();
247:
248: try {
249: Setting s = resolve(path);
250:
251: if (s == null)
252: return false;
253:
254: remove(dbms, s);
255:
256: return true;
257: } finally {
258: lock.writeLock().unlock();
259: }
260: }
261:
262: //---------------------------------------------------------------------------
263:
264: public boolean removeChildren(Dbms dbms, String path)
265: throws SQLException {
266: lock.writeLock().lock();
267:
268: try {
269: Setting s = resolve(path);
270:
271: if (s == null)
272: return false;
273:
274: for (Setting child : s.getChildren())
275: remove(dbms, child);
276:
277: return true;
278: } finally {
279: lock.writeLock().unlock();
280: }
281: }
282:
283: //---------------------------------------------------------------------------
284: //--- Auxiliary methods
285: //---------------------------------------------------------------------------
286:
287: public boolean getValueAsBool(String path, boolean defValue) {
288: String value = getValue(path);
289:
290: return (value != null) ? value.equals("true") : defValue;
291: }
292:
293: //---------------------------------------------------------------------------
294:
295: public boolean getValueAsBool(String path) {
296: String value = getValue(path);
297:
298: if (value == null)
299: return false;
300:
301: return value.equals("true");
302: }
303:
304: //---------------------------------------------------------------------------
305:
306: public Integer getValueAsInt(String path) {
307: String value = getValue(path);
308:
309: if (value == null || value.trim().length() == 0)
310: return null;
311:
312: return new Integer(value);
313: }
314:
315: //---------------------------------------------------------------------------
316: //---
317: //--- Private methods
318: //---
319: //---------------------------------------------------------------------------
320:
321: private void createSubTree(Setting s, List elemList) {
322: for (Iterator i = elemList.iterator(); i.hasNext();) {
323: Element elem = (Element) i.next();
324: String sParId = elem.getChildText("parentid");
325: int parId = sParId.equals("") ? -1 : Integer
326: .parseInt(sParId);
327:
328: if (s.getId() == parId) {
329: String id = elem.getChildText("id");
330: String name = elem.getChildText("name");
331: String value = elem.getChildText("value");
332:
333: Setting child = new Setting(Integer.parseInt(id), name,
334: value);
335:
336: s.addChild(child);
337: i.remove();
338: }
339: }
340:
341: for (Setting child : s.getChildren())
342: createSubTree(child, elemList);
343: }
344:
345: //---------------------------------------------------------------------------
346:
347: private String makeString(Object obj) {
348: return (obj == null) ? null : obj.toString();
349: }
350:
351: //---------------------------------------------------------------------------
352:
353: private Setting resolve(String path) {
354: StringTokenizer st = new StringTokenizer(path, SEPARATOR);
355:
356: Setting s = root;
357:
358: while (s != null && st.hasMoreTokens()) {
359: String child = st.nextToken();
360:
361: if (child.startsWith("id:"))
362: s = find(s, Integer.parseInt(child.substring(3)));
363: else
364: s = s.getChild(child);
365: }
366:
367: return s;
368: }
369:
370: //---------------------------------------------------------------------------
371:
372: private Setting find(Setting s, int id) {
373: ArrayList<Setting> stack = new ArrayList<Setting>();
374:
375: for (Setting child : s.getChildren())
376: stack.add(child);
377:
378: while (!stack.isEmpty()) {
379: s = stack.get(0);
380: stack.remove(0);
381:
382: if (s.getId() == id)
383: return s;
384:
385: for (Setting child : s.getChildren())
386: stack.add(child);
387: }
388:
389: return null;
390: }
391:
392: //---------------------------------------------------------------------------
393:
394: private Setting findAmongAdded(Dbms dbms, String path) {
395: if (!path.startsWith("id:"))
396: return null;
397:
398: if (path.indexOf(SEPARATOR) != -1)
399: return null;
400:
401: int id = Integer.parseInt(path.substring(3));
402:
403: for (Task task : tasks) {
404: Setting s = task.getAddedSetting(dbms, id);
405:
406: if (s != null)
407: return s;
408: }
409:
410: return null;
411: }
412:
413: //---------------------------------------------------------------------------
414:
415: private Element build(Setting s, int level) {
416: Element el = new Element(s.getName());
417: el.setAttribute("id", Integer.toString(s.getId()));
418:
419: if (s.getValue() != null) {
420: Element value = new Element("value");
421: value.setText(s.getValue());
422:
423: el.addContent(value);
424: }
425:
426: if (level != 0) {
427: Element children = new Element("children");
428:
429: for (Setting child : s.getChildren())
430: children.addContent(build(child, level - 1));
431:
432: if (children.getContentSize() != 0)
433: el.addContent(children);
434: }
435:
436: return el;
437: }
438:
439: //---------------------------------------------------------------------------
440:
441: private int getNextSerial(Dbms dbms) throws SQLException {
442: if (maxSerial == 0) {
443: List list = dbms.select(
444: "SELECT MAX(id) AS max FROM Settings")
445: .getChildren();
446: String max = ((Element) list.get(0)).getChildText("max");
447:
448: maxSerial = Integer.parseInt(max);
449: }
450:
451: return ++maxSerial;
452: }
453:
454: //---------------------------------------------------------------------------
455:
456: private void remove(Dbms dbms, Setting s) throws SQLException {
457: for (Setting child : s.getChildren())
458: remove(dbms, child);
459:
460: dbms.execute("DELETE FROM Settings WHERE id=?", s.getId());
461: tasks.add(Task.getRemovedTask(dbms, s));
462: }
463:
464: //---------------------------------------------------------------------------
465: //---
466: //--- ResourceListener interface
467: //---
468: //---------------------------------------------------------------------------
469:
470: private void flush(Object resource, boolean commit) {
471: lock.writeLock().lock();
472:
473: try {
474: for (Iterator<Task> i = tasks.iterator(); i.hasNext();) {
475: Task task = i.next();
476:
477: if (task.matches(resource)) {
478: i.remove();
479:
480: if (commit)
481: task.commit();
482: }
483: }
484: } finally {
485: lock.writeLock().unlock();
486: }
487: }
488:
489: //---------------------------------------------------------------------------
490: //---
491: //--- Vars
492: //---
493: //---------------------------------------------------------------------------
494:
495: private static final String SEPARATOR = "/";
496:
497: private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
498:
499: private List<Task> tasks = new ArrayList<Task>();
500:
501: private int maxSerial = 0;
502:
503: //---------------------------------------------------------------------------
504:
505: private ResourceListener resList = new ResourceListener() {
506: public void close(Object resource) {
507: flush(resource, true);
508: }
509:
510: public void abort(Object resource) {
511: flush(resource, false);
512: }
513: };
514: }
515:
516: //=============================================================================
|