001: // serverAbstractSwitch.java
002: // -------------------------------------
003: // (C) by Michael Peter Christen; mc@anomic.de
004: // first published on http://www.anomic.de
005: // Frankfurt, Germany, 2004, 2005
006: // last major change: 24.03.2005
007: //
008: // This program is free software; you can redistribute it and/or modify
009: // it under the terms of the GNU General Public License as published by
010: // the Free Software Foundation; either version 2 of the License, or
011: // (at your option) any later version.
012: //
013: // This program is distributed in the hope that it will be useful,
014: // but WITHOUT ANY WARRANTY; without even the implied warranty of
015: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: // GNU General Public License for more details.
017: //
018: // You should have received a copy of the GNU General Public License
019: // along with this program; if not, write to the Free Software
020: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: //
022: // Using this software in any meaning (reading, learning, copying, compiling,
023: // running) means that you agree that the Author(s) is (are) not responsible
024: // for cost, loss of data or any harm that may be caused directly or indirectly
025: // by usage of this softare or this documentation. The usage of this software
026: // is on your own risk. The installation and usage (starting/running) of this
027: // software may allow other people or application to access your computer and
028: // any attached devices and is highly dependent on the configuration of the
029: // software which must be done by the user of the software; the author(s) is
030: // (are) also not responsible for proper configuration and usage of the
031: // software, even if provoked by documentation provided together with
032: // the software.
033: //
034: // Any changes to this file according to the GPL as documented in the file
035: // gpl.txt aside this file in the shipment you received can be done to the
036: // lines that follows this copyright notice here, but changes must not be
037: // done inside the copyright notive above. A re-distribution must contain
038: // the intact and unchanged copyright notice.
039: // Contributions and changes to the program code must be marked as such.
040:
041: package de.anomic.server;
042:
043: import java.io.File;
044: import java.io.IOException;
045: import java.net.InetAddress;
046: import java.util.ConcurrentModificationException;
047: import java.util.HashMap;
048: import java.util.Iterator;
049: import java.util.Map;
050: import java.util.TreeMap;
051:
052: import de.anomic.server.logging.serverLog;
053:
054: public abstract class serverAbstractSwitch implements serverSwitch {
055:
056: private static final long maxTrackingTimeDefault = 1000 * 60 * 60; // store only access data from the last hour to save ram space
057:
058: // configuration management
059: private File configFile;
060: private String configComment;
061: private File rootPath;
062: protected serverLog log;
063: protected int serverJobs;
064: protected long maxTrackingTime;
065: private Map<String, String> configProps;
066: private Map<String, String> configRemoved;
067: private HashMap<InetAddress, String> authorization;
068: private TreeMap<String, serverThread> workerThreads;
069: private TreeMap<String, serverSwitchAction> switchActions;
070: protected HashMap<String, TreeMap<Long, String>> accessTracker; // mappings from requesting host to an ArrayList of serverTrack-entries
071:
072: public serverAbstractSwitch(File rootPath, String initPath,
073: String configPath, boolean applyPro) {
074: // we initialize the switchboard with a property file,
075: // but maintain these properties then later in a new 'config' file
076: // to reset all changed configs, the config file must
077: // be deleted, but not the init file
078: // the only attribute that will always be read from the init is the
079: // file name of the config file
080: this .rootPath = rootPath;
081: configComment = "This is an automatically generated file, updated by serverAbstractSwitch and initialized by "
082: + initPath;
083: File initFile = new File(rootPath, initPath);
084: configFile = new File(rootPath, configPath); // propertiesFile(config);
085: new File(configFile.getParent()).mkdir();
086:
087: // predefine init's
088: Map<String, String> initProps;
089: if (initFile.exists())
090: initProps = serverFileUtils.loadHashMap(initFile);
091: else
092: initProps = new HashMap<String, String>();
093:
094: // if 'pro'-version is selected, overload standard settings with 'pro'-settings
095: Iterator<String> i;
096: String prop;
097: if (applyPro) {
098: i = new HashMap<String, String>(initProps).keySet()
099: .iterator(); // clone the map to avoid concurrent modification exceptions
100: while (i.hasNext()) {
101: prop = (String) i.next();
102: if (prop.endsWith("__pro")) {
103: initProps.put(prop.substring(0, prop.length() - 5),
104: initProps.get(prop));
105: }
106: }
107: }
108: // delete the 'pro' init settings
109: i = initProps.keySet().iterator();
110: while (i.hasNext()) {
111: prop = (String) i.next();
112: if (prop.endsWith("__pro")) {
113: i.remove();
114: }
115: }
116:
117: // load config's from last save
118: if (configFile.exists())
119: configProps = serverFileUtils.loadHashMap(configFile);
120: else
121: configProps = new HashMap<String, String>();
122:
123: // remove all values from config that do not appear in init
124: configRemoved = new HashMap<String, String>();
125: synchronized (configProps) {
126: i = configProps.keySet().iterator();
127: String key;
128: while (i.hasNext()) {
129: key = i.next();
130: if (!(initProps.containsKey(key))) {
131: configRemoved.put(key, this .configProps.get(key));
132: i.remove();
133: }
134: }
135:
136: // doing a config settings migration
137: //HashMap migratedSettings = migrateSwitchConfigSettings((HashMap) removedProps);
138: //if (migratedSettings != null) configProps.putAll(migratedSettings);
139:
140: // merge new props from init to config
141: // this is necessary for migration, when new properties are attached
142: initProps.putAll(configProps);
143: configProps = initProps;
144:
145: // save result; this may initially create a config file after
146: // initialization
147: saveConfig();
148: }
149:
150: // other settings
151: authorization = new HashMap<InetAddress, String>();
152: accessTracker = new HashMap<String, TreeMap<Long, String>>();
153:
154: // init thread control
155: workerThreads = new TreeMap<String, serverThread>();
156:
157: // init switch actions
158: switchActions = new TreeMap<String, serverSwitchAction>();
159:
160: // init busy state control
161: serverJobs = 0;
162:
163: // init server tracking
164: maxTrackingTime = getConfigLong("maxTrackingTime",
165: maxTrackingTimeDefault);
166: }
167:
168: // a logger for this switchboard
169: public void setLog(serverLog log) {
170: this .log = log;
171: }
172:
173: public serverLog getLog() {
174: return log;
175: }
176:
177: public void track(String host, String accessPath) {
178: // learn that a specific host has accessed a specific path
179: if (accessPath == null)
180: accessPath = "NULL";
181: TreeMap<Long, String> access = accessTracker.get(host);
182: if (access == null)
183: access = new TreeMap<Long, String>();
184: synchronized (access) {
185: access
186: .put(new Long(System.currentTimeMillis()),
187: accessPath);
188:
189: // write back to tracker
190: try {
191: accessTracker.put(host, clearTooOldAccess(access));
192: } catch (ConcurrentModificationException e) {
193: }
194: ;
195: }
196: }
197:
198: public TreeMap<Long, String> accessTrack(String host) {
199: // returns mapping from Long(accesstime) to path
200:
201: TreeMap<Long, String> access = accessTracker.get(host);
202: if (access == null)
203: return null;
204: synchronized (access) {
205: // clear too old entries
206: int oldsize = access.size();
207: if ((access = clearTooOldAccess(access)).size() != oldsize) {
208: // write back to tracker
209: if (access.size() == 0) {
210: accessTracker.remove(host);
211: } else {
212: accessTracker.put(host, access);
213: }
214: }
215:
216: return access;
217: }
218: }
219:
220: private TreeMap<Long, String> clearTooOldAccess(
221: TreeMap<Long, String> access) {
222: return new TreeMap<Long, String>(access.tailMap(new Long(System
223: .currentTimeMillis()
224: - maxTrackingTime)));
225: }
226:
227: public Iterator<String> accessHosts() {
228: // returns an iterator of hosts in tracker (String)
229: HashMap<String, TreeMap<Long, String>> accessTrackerClone = new HashMap<String, TreeMap<Long, String>>();
230: try {
231: accessTrackerClone.putAll(accessTracker);
232: } catch (ConcurrentModificationException e) {
233: }
234: return accessTrackerClone.keySet().iterator();
235: }
236:
237: public void setConfig(Map<String, String> otherConfigs) {
238: Iterator<Map.Entry<String, String>> i = otherConfigs.entrySet()
239: .iterator();
240: Map.Entry<String, String> entry;
241: while (i.hasNext()) {
242: entry = i.next();
243: setConfig(entry.getKey(), entry.getValue());
244: }
245: }
246:
247: public void setConfig(String key, boolean value) {
248: setConfig(key, (value) ? "true" : "false");
249: }
250:
251: public void setConfig(String key, long value) {
252: setConfig(key, Long.toString(value));
253: }
254:
255: public void setConfig(String key, double value) {
256: setConfig(key, Double.toString(value));
257: }
258:
259: public void setConfig(String key, String value) {
260: // perform action before setting new value
261: Iterator<serverSwitchAction> bevore = switchActions.values()
262: .iterator();
263: Iterator<serverSwitchAction> after = switchActions.values()
264: .iterator();
265: synchronized (configProps) {
266: serverSwitchAction action;
267:
268: while (bevore.hasNext()) {
269: action = bevore.next();
270: try {
271: action.doBevoreSetConfig(key, value);
272: } catch (Exception e) {
273: log.logSevere("serverAction bevoreSetConfig '"
274: + action.getShortDescription()
275: + "' failed with exception: "
276: + e.getMessage());
277: }
278: }
279:
280: // set the value
281: Object oldValue = configProps.put(key, value);
282: saveConfig();
283:
284: // perform actions afterwards
285: while (after.hasNext()) {
286: action = after.next();
287: try {
288: action.doAfterSetConfig(key, value,
289: (oldValue == null) ? null
290: : (String) oldValue);
291: } catch (Exception e) {
292: log.logSevere("serverAction afterSetConfig '"
293: + action.getShortDescription()
294: + "' failed with exception: "
295: + e.getMessage());
296: }
297: }
298: }
299: }
300:
301: public String getConfig(String key, String dflt) {
302: Iterator<serverSwitchAction> i = switchActions.values()
303: .iterator();
304: synchronized (configProps) {
305: // get the value
306: Object s = configProps.get(key);
307:
308: // do action
309: serverSwitchAction action;
310: while (i.hasNext()) {
311: action = i.next();
312: try {
313: action.doWhenGetConfig(key, (s == null) ? null
314: : (String) s, dflt);
315: } catch (Exception e) {
316: log.logSevere("serverAction whenGetConfig '"
317: + action.getShortDescription()
318: + "' failed with exception: "
319: + e.getMessage());
320: }
321: }
322:
323: // return value
324: if (s == null)
325: return dflt;
326: else
327: return (String) s;
328: }
329: }
330:
331: public long getConfigLong(String key, long dflt) {
332: try {
333: return Long.parseLong(getConfig(key, Long.toString(dflt)));
334: } catch (NumberFormatException e) {
335: return dflt;
336: }
337: }
338:
339: public double getConfigDouble(String key, double dflt) {
340: try {
341: return Double.parseDouble(getConfig(key, Double
342: .toString(dflt)));
343: } catch (NumberFormatException e) {
344: return dflt;
345: }
346: }
347:
348: public boolean getConfigBool(String key, boolean dflt) {
349: return Boolean.valueOf(getConfig(key, Boolean.toString(dflt)))
350: .booleanValue();
351: }
352:
353: /**
354: * Create a File instance for a configuration setting specifying a path.
355: * @param key config key
356: * @param dflt default path value, that is used when there is no value
357: * <code>key</code> in the configuration.
358: * @return if the value of the setting is an absolute path String, then the
359: * returned File is derived from this setting only. Otherwise the path's file
360: * is constructed from the applications root path + the relative path setting.
361: */
362: public File getConfigPath(String key, String dflt) {
363: File ret;
364: String path = getConfig(key, dflt).replace('\\', '/');
365: File f = new File(path);
366: if (f == null) {
367: ret = null;
368: } else {
369: ret = (f.isAbsolute() ? f : new File(this .rootPath, path));
370: }
371:
372: return ret;
373: }
374:
375: public Iterator<String> configKeys() {
376: return configProps.keySet().iterator();
377: }
378:
379: private void saveConfig() {
380: try {
381: synchronized (configProps) {
382: serverFileUtils.saveMap(configFile, configProps,
383: configComment);
384: }
385: } catch (IOException e) {
386: System.out.println("ERROR: cannot write config file "
387: + configFile.toString() + ": " + e.getMessage());
388: }
389: }
390:
391: public Map<String, String> getRemoved() {
392: // returns configuration that had been removed during initialization
393: return configRemoved;
394: }
395:
396: // add/remove action listener
397: public void deployAction(String actionName,
398: String actionShortDescription,
399: String actionLongDescription, serverSwitchAction newAction) {
400: newAction.setLog(log);
401: newAction.setDescription(actionShortDescription,
402: actionLongDescription);
403: switchActions.put(actionName, newAction);
404: log.logInfo("Deployed Action '" + actionShortDescription
405: + "', (" + switchActions.size()
406: + " actions registered)");
407: }
408:
409: public void undeployAction(String actionName) {
410: serverSwitchAction action = (serverSwitchAction) switchActions
411: .get(actionName);
412: action.close();
413: switchActions.remove(actionName);
414: log.logInfo("Undeployed Action '"
415: + action.getShortDescription() + "', ("
416: + switchActions.size() + " actions registered)");
417: }
418:
419: public void deployThread(String threadName,
420: String threadShortDescription,
421: String threadLongDescription, String threadMonitorURL,
422: serverThread newThread, long startupDelay) {
423: deployThread(threadName, threadShortDescription,
424: threadLongDescription, threadMonitorURL, newThread,
425: startupDelay, Long.parseLong(getConfig(threadName
426: + "_idlesleep", "100")), Long
427: .parseLong(getConfig(threadName + "_busysleep",
428: "1000")), Long.parseLong(getConfig(
429: threadName + "_memprereq", "1000000")));
430: }
431:
432: public void deployThread(String threadName,
433: String threadShortDescription,
434: String threadLongDescription, String threadMonitorURL,
435: serverThread newThread, long startupDelay,
436: long initialIdleSleep, long initialBusySleep,
437: long initialMemoryPreRequisite) {
438: if (newThread.isAlive())
439: throw new RuntimeException(
440: "undeployed threads must not live; they are started as part of the deployment");
441: newThread.setStartupSleep(startupDelay);
442: long x;
443: try {
444: x = Long.parseLong(getConfig(threadName + "_idlesleep",
445: "novalue"));
446: newThread.setIdleSleep(x);
447: } catch (NumberFormatException e) {
448: newThread.setIdleSleep(initialIdleSleep);
449: setConfig(threadName + "_idlesleep", initialIdleSleep);
450: }
451: try {
452: x = Long.parseLong(getConfig(threadName + "_busysleep",
453: "novalue"));
454: newThread.setBusySleep(x);
455: } catch (NumberFormatException e) {
456: newThread.setBusySleep(initialBusySleep);
457: setConfig(threadName + "_busysleep", initialBusySleep);
458: }
459: try {
460: x = Long.parseLong(getConfig(threadName + "_memprereq",
461: "novalue"));
462: newThread.setMemPreReqisite(x);
463: } catch (NumberFormatException e) {
464: newThread.setMemPreReqisite(initialMemoryPreRequisite);
465: setConfig(threadName + "_memprereq",
466: initialMemoryPreRequisite);
467: }
468: newThread.setLog(log);
469: newThread.setDescription(threadShortDescription,
470: threadLongDescription, threadMonitorURL);
471: workerThreads.put(threadName, newThread);
472: // start the thread
473: if (workerThreads.containsKey(threadName))
474: newThread.start();
475: }
476:
477: public serverThread getThread(String threadName) {
478: return (serverThread) workerThreads.get(threadName);
479: }
480:
481: public void setThreadPerformance(String threadName,
482: long idleMillis, long busyMillis, long memprereqBytes) {
483: serverThread thread = (serverThread) workerThreads
484: .get(threadName);
485: if (thread != null) {
486: thread.setIdleSleep(idleMillis);
487: thread.setBusySleep(busyMillis);
488: thread.setMemPreReqisite(memprereqBytes);
489: }
490: }
491:
492: public synchronized void terminateThread(String threadName,
493: boolean waitFor) {
494: if (workerThreads.containsKey(threadName)) {
495: ((serverThread) workerThreads.get(threadName))
496: .terminate(waitFor);
497: workerThreads.remove(threadName);
498: }
499: }
500:
501: public void intermissionAllThreads(long pause) {
502: Iterator<String> e = workerThreads.keySet().iterator();
503: while (e.hasNext()) {
504: ((serverThread) workerThreads.get(e.next()))
505: .intermission(pause);
506: }
507: }
508:
509: public synchronized void terminateAllThreads(boolean waitFor) {
510: Iterator<String> e = workerThreads.keySet().iterator();
511: while (e.hasNext()) {
512: ((serverThread) workerThreads.get(e.next()))
513: .terminate(false);
514: }
515: if (waitFor) {
516: e = workerThreads.keySet().iterator();
517: while (e.hasNext()) {
518: ((serverThread) workerThreads.get(e.next()))
519: .terminate(true);
520: e.remove();
521: }
522: }
523: }
524:
525: public Iterator<String> /*of serverThread-Names (String)*/threadNames() {
526: return workerThreads.keySet().iterator();
527: }
528:
529: abstract public int queueSize();
530:
531: abstract public void enQueue(Object job);
532:
533: abstract public boolean deQueue();
534:
535: // authentification routines:
536:
537: public void setAuthentify(InetAddress host, String user,
538: String rights) {
539: // sets access attributes according to host addresses
540: authorization.put(host, user + "@" + rights);
541: }
542:
543: public void removeAuthentify(InetAddress host) {
544: // remove access attributes according to host addresses
545: authorization.remove(host);
546: }
547:
548: public String getAuthentifyUser(InetAddress host) {
549: // read user name according to host addresses
550: String a = (String) authorization.get(host);
551: if (a == null)
552: return null;
553: int p = a.indexOf("@");
554: if (p < 0)
555: return null;
556: return a.substring(0, p);
557: }
558:
559: public String getAuthentifyRights(InetAddress host) {
560: // read access rigths according to host addresses
561: String a = (String) authorization.get(host);
562: if (a == null)
563: return null;
564: int p = a.indexOf("@");
565: if (p < 0)
566: return null;
567: return a.substring(p + 1);
568: }
569:
570: public void addAuthentifyRight(InetAddress host, String right) {
571: String rights = getAuthentifyRights(host);
572: if (rights == null) {
573: // create new authentification
574: setAuthentify(host, "unknown", right);
575: } else {
576: // add more authentification
577: String user = getAuthentifyUser(host);
578: setAuthentify(host, user, rights + right);
579: }
580: }
581:
582: public boolean hasAuthentifyRight(InetAddress host, String right) {
583: String rights = getAuthentifyRights(host);
584: if (rights == null)
585: return false;
586: return rights.indexOf(right) >= 0;
587: }
588:
589: public abstract serverObjects action(String actionName,
590: serverObjects actionInput);
591:
592: public File getRootPath() {
593: return rootPath;
594: }
595:
596: public String toString() {
597: return configProps.toString();
598: }
599:
600: public void handleBusyState(int jobs) {
601: serverJobs = jobs;
602: }
603: }
|