1 package org.molwind.util;
2
3 /*
4 * This file is part of Molwind.
5 *
6 * Molwind is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Molwind is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Molwind. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.math.BigDecimal;
26 import java.sql.Timestamp;
27 import java.util.ArrayList;
28 import java.util.Enumeration;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Observable;
32 import java.util.Properties;
33 import java.util.Stack;
34
35 import javax.servlet.ServletContext;
36
37 import org.apache.commons.configuration.Configuration;
38 import org.apache.commons.configuration.ConfigurationException;
39 import org.apache.commons.configuration.XMLConfiguration;
40
41 //import org.molwind.io.SDFileLocator;
42 import org.molwind.io.BasePathSupport;
43 import org.molwind.io.WorldLocator;
44 import org.molwind.model.MolwindWorld;
45
46 /**
47 * Holds the configuration options of the molwind server. This is a singleton as
48 * there is only one per server.
49 * <p>
50 *
51 * @author <a href="mailto:oliver.karch@molwind.org">Oliver Karch</a>
52 * @version 1.0
53 */
54 public final class MolwindServerConfiguration extends Observable
55 implements WorldLocator, Configurator {
56
57 // private ArrayList<String> al = new ArrayList<String>();
58 // private static ArrayList<String> url=
59 // new ArrayList<String>(), urlXML=new ArrayList<String>();
60 // private static HashMap<String, Integer>names=
61 // new HashMap<String, Integer>();
62 // private static HashMap<Integer,String>names2=
63 // new HashMap<Integer,String>();
64 // private static ArrayList<String>features=new ArrayList<String>();
65 // private static worldCount=1;
66 // private static String server;
67 // private String confPath;
68 // private static HashMap<Integer, Integer> isStarted=
69 // new HashMap<Integer,Integer>();
70
71 private String path;
72 private int moleculesPerLevel;
73 private long lastModified;
74 private double zoom;
75 private boolean intermediateLayer;
76 private ArrayList<String> worldPaths;
77 private ArrayList worldLocators;
78 private Stack configStack;
79 private ServletContext servletContext;
80
81 private static MolwindServerConfiguration config;
82
83 /**
84 * Default maximum number of molecules per level.
85 */
86 public static final int DEFAULT_MOLECULES_PER_LEVEL = 180;
87
88 /**
89 * Default zoom factor.
90 */
91 public static final double DEFAULT_ZOOM = 0.5d;
92
93 /**
94 * Default intermediate layer flag.
95 */
96 public static final boolean DEFAULT_INTERMEDIATE_LAYER = false;
97
98
99 private MolwindServerConfiguration() {
100 this(null);
101 }
102
103 private MolwindServerConfiguration(final String newPath) {
104 super();
105 path = newPath;
106 moleculesPerLevel = DEFAULT_MOLECULES_PER_LEVEL;
107 zoom = DEFAULT_ZOOM;
108 lastModified = 0L;
109 configStack = new Stack();
110 worldPaths = new ArrayList<String>();
111 worldLocators = new ArrayList();
112 initWorldPaths();
113
114 }
115
116 private void initWorldPaths() {
117 String userPath = System.getProperty("user.dir");
118 if (userPath != null) {
119 addWorldPath(userPath);
120 }
121
122 String userHomePath = System.getProperty("user.home");
123 if (userHomePath != null) {
124 addWorldPath(userHomePath);
125 }
126
127 String javaPath = System.getProperty("java.class.path");
128 String[] javaPathParts = javaPath.split(File.pathSeparator);
129
130 for (int i = 0; i < javaPathParts.length; i++) {
131 File theFile = new File(javaPathParts[i]);
132
133 if (theFile.isDirectory()) {
134 addWorldPath(javaPathParts[i]);
135 } else {
136 theFile = theFile.getParentFile();
137 if (theFile != null) {
138 addWorldPath(theFile.getPath());
139 }
140 }
141 }
142 }
143
144
145 /**
146 * Creates a new configuration object and initializes it with default
147 * values (lazily loaded).
148 *
149 * @param path
150 * the path to the configuration file
151 * @return
152 * the configuration instance
153 */
154 public static MolwindServerConfiguration getInstance(final String path) {
155 if (config == null) {
156 config = new MolwindServerConfiguration(path);
157 }
158 return config;
159 }
160
161 /**
162 * Creates a new configuration object and initializes it with default
163 * values (lazily loaded).
164 *
165 * @return
166 * the configuration instance
167 */
168 public static MolwindServerConfiguration getInstance() {
169 return getInstance(null);
170 }
171
172
173 private void pushConfig(final Configuration cfg, final String startKey)
174 throws ConfigurationException {
175 Iterator pit = cfg.getKeys(startKey);
176 Properties properties = new Properties();
177 configStack.push(properties);
178
179 while (pit.hasNext()) {
180 String kn = (String) pit.next();
181 String an = Stringx.after(kn, startKey + ".");
182 String kv = cfg.getString(kn);
183
184 if (kv != null) {
185 properties.put(an, kv);
186 }
187 }
188 }
189
190 private void load()
191 throws IOException {
192 File theFile = null;
193
194 if (path != null) {
195 theFile = new File(path);
196
197 if (theFile.exists() && (theFile.lastModified() > lastModified)) {
198 MolwindLogger.info("Loading config from " + theFile);
199
200 try {
201 XMLConfiguration xmlConfig = new XMLConfiguration(theFile);
202
203 setMoleculesPerLevel(xmlConfig.getInt("moleculesPerLevel",
204 DEFAULT_MOLECULES_PER_LEVEL));
205 setZoom(xmlConfig.getDouble("zoom", DEFAULT_ZOOM));
206 setIntermediateLayer(xmlConfig.getBoolean(
207 "intermediateLayer", DEFAULT_INTERMEDIATE_LAYER));
208
209 List list = xmlConfig.getList("locators.locator.class");
210
211 Iterator it = list.iterator();
212 clearWorldLocators();
213
214 for (int i = 0; it.hasNext(); i++) {
215 String className = (String) it.next();
216 String pathNames= xmlConfig.getString("locators.locator.params.pathNames");
217
218 try {
219 Class clazz = Class.forName(className);
220 WorldLocator locator =
221 (WorldLocator) clazz.newInstance();
222
223 if (locator instanceof Configurable) {
224 pushConfig(xmlConfig,
225 "locators.locator(" + i + ").params");
226 try {
227 ((Configurable) locator).configure(this);
228 } catch (ConfigException ce) {
229 throw new IOException(ce.getMessage());
230 }
231 }
232 if( locator instanceof BasePathSupport ) {
233 BasePathSupport bps = (BasePathSupport)locator;
234 for(String s: worldPaths ){
235 bps.addBasePath( s );
236 }
237
238 }
239 /** if(locator instanceof SDFileLocator){
240 * SDFileLocator sdfl = (SDFileLocator)locator;
241 * sdfl.setWorldPaths(pathNames);
242 *}
243 */
244
245 addWorldLocator(locator);
246 } catch (ClassNotFoundException cnfe) {
247 MolwindLogger.warn("Cannot find locator "
248 + className, cnfe);
249 } catch (InstantiationException iex) {
250 MolwindLogger.warn("Cannot instantiate "
251 + className, iex);
252 } catch (IllegalAccessException iax) {
253 MolwindLogger.warn("Cannot instantiate "
254 + className, iax);
255 }
256 }
257 lastModified = theFile.lastModified();
258 } catch (ConfigurationException ce) {
259 throw new IOException(ce.getMessage());
260 }
261 }
262 }
263 }
264
265 private void ensureLoad() {
266 try {
267 load();
268 } catch (IOException ioe) {
269 MolwindLogger.warn(ioe);
270 }
271 }
272
273
274 /**
275 * Get the Path value.
276 *
277 * @return
278 * the Path value
279 */
280 public String getPath() {
281 ensureLoad();
282 return path;
283 }
284
285 /**
286 * Set the Path value.
287 *
288 * @param newPath
289 * the new Path value
290 */
291 public void setPath(final String newPath) {
292 this.path = newPath;
293 lastModified = 0L;
294 }
295
296 /**
297 * Get the MoleculesPerLevel value.
298 *
299 * @return
300 * the MoleculesPerLevel value
301 */
302 public int getMoleculesPerLevel() {
303 ensureLoad();
304 return moleculesPerLevel;
305 }
306
307 /**
308 * Set the MoleculesPerLevel value.
309 *
310 * @param newMoleculesPerLevel
311 * the new MoleculesPerLevel value
312 */
313 public void setMoleculesPerLevel(final int newMoleculesPerLevel) {
314 this.moleculesPerLevel = newMoleculesPerLevel;
315 }
316
317 /**
318 * Get the Zoom value.
319 *
320 * @return
321 * the Zoom value
322 */
323 public double getZoom() {
324 ensureLoad();
325 return zoom;
326 }
327
328 /**
329 * Set the Zoom value.
330 *
331 * @param newZoom
332 * the new Zoom value
333 */
334 public void setZoom(final double newZoom) {
335 this.zoom = newZoom;
336 }
337
338 /**
339 * Get the IntermediateLayer value.
340 *
341 * @return
342 * the IntermediateLayer value
343 */
344 public boolean isIntermediateLayer() {
345 ensureLoad();
346 return intermediateLayer;
347 }
348
349 /**
350 * Set the IntermediateLayer value.
351 *
352 * @param newIntermediateLayer
353 * the new IntermediateLayer value
354 */
355 public void setIntermediateLayer(final boolean newIntermediateLayer) {
356 this.intermediateLayer = newIntermediateLayer;
357 }
358
359 /**
360 * Adds a search path for world data to locate.
361 *
362 * @param searchPath
363 * a path where world data can be found
364 */
365 public void addWorldPath(final String searchPath) {
366 worldPaths.add(searchPath);
367
368 }
369
370 /**
371 * Returns an array of search paths for worlds.
372 *
373 * @return
374 * array of search paths
375 */
376 public String[] getWorldPaths() {
377 ensureLoad();
378 String[] paths = new String[worldPaths.size()];
379 return (String[]) worldPaths.toArray(paths);
380 }
381
382
383 private void clearWorldLocators() {
384 worldLocators.clear();
385 }
386
387
388 /**
389 * Adds a world locator used to find worlds.
390 *
391 * @param locator
392 * the locator to be added
393 */
394 public void addWorldLocator(final WorldLocator locator) {
395 worldLocators.add(locator);
396 }
397
398 /**
399 * Returns an array of world locators.
400 *
401 * @return
402 * an array of locators
403 */
404 public WorldLocator[] getWorldLocators() {
405 ensureLoad();
406 WorldLocator[] locators = new WorldLocator[worldLocators.size()];
407 return (WorldLocator[]) worldLocators.toArray(locators);
408 }
409
410
411 private String toWorldPath(final String prefix, final String suffix) {
412 File theFile = new File(prefix);
413
414 if (!theFile.exists() && (servletContext != null)) {
415 String scPath = servletContext.getRealPath("/");
416 theFile = new File(scPath, prefix);
417 if (theFile.exists()) {
418 return (new File(theFile, suffix)).getPath();
419 }
420 }
421
422 String worldPath = prefix + (prefix.endsWith("/") ? "" : "/") + suffix;
423
424 return worldPath;
425 }
426
427
428 /**
429 * Locates a world with the given name.
430 *
431 * @param worldName
432 * the name of the world
433 * @return
434 * a molwind world descriptor
435 * @throws java.io.IOException
436 * is thrown upon i/o error
437 */
438 public MolwindWorld locateWorld(final String worldName)
439 throws IOException {
440 ensureLoad();
441 MolwindWorld world = null;
442 WorldLocator[] locators = getWorldLocators();
443 String[] worldPaths = getWorldPaths();
444
445 for (int i = 0; i < locators.length; i++) {
446 for (int j = 0; j < worldPaths.length; j++) {
447 world = locators[i].locateWorld(toWorldPath(worldPaths[j],
448 worldName));
449 if (world != null) {
450 return world;
451 }
452 }
453 }
454
455 return null;
456 }
457
458 /**
459 * Get the ServletContext value.
460 *
461 * @return
462 * the ServletContext value
463 */
464 public ServletContext getServletContext() {
465 return servletContext;
466 }
467
468 /**
469 * Set the ServletContext value.
470 *
471 * @param newServletContext
472 * the new ServletContext value
473 */
474 public void setServletContext(final ServletContext newServletContext) {
475 this.servletContext = newServletContext;
476 }
477
478
479 private BigDecimal convertTimestamp(final String pVal) {
480 BigDecimal bDec = null;
481 try {
482 Timestamp timestamp = Timestamp.valueOf(pVal);
483 bDec = BigDecimal.valueOf(timestamp.getTime());
484 } catch (IllegalArgumentException iae) {
485 bDec = null;
486 }
487 return bDec;
488 }
489
490 private BigDecimal numberInit(final String pVal) {
491 if (pVal.length() <= 0) {
492 return BigDecimal.ZERO;
493 }
494
495 BigDecimal bDec = null;
496 try {
497 bDec = new BigDecimal(pVal);
498 } catch (NumberFormatException nfe) {
499 // Try special cases of numbers, such as timestamp, dates, times
500 bDec = convertTimestamp(pVal);
501 }
502 if (bDec == null) {
503 throw new NumberFormatException();
504 }
505 return bDec;
506 }
507
508 private Object[] createParam(final Class pType, final String pVal) {
509 Object[] param = new Object[1];
510
511 if (pType.equals(Boolean.class) || pType.equals(Boolean.TYPE)) {
512 param[0] = Boolean.valueOf(Stringx.toBoolean(pVal, false));
513 } else if (pType.equals(Character.class)
514 || pType.equals(Character.TYPE)) {
515 param[0] = Character.valueOf(
516 ((pVal.length() > 0) ? pVal.charAt(0) : (char) 0)
517 );
518 } else if (pType.equals(Byte.class) || pType.equals(Byte.TYPE)) {
519 if (pVal.matches("0[01]+") || pVal.matches("[01]{4,8}+")) {
520 param[0] = Byte.valueOf(Byte.parseByte(pVal, 2));
521 } else {
522 param[0] = Byte.valueOf(pVal);
523 }
524 } else if (pType.equals(Short.class) || pType.equals(Short.TYPE)) {
525 param[0] = Short.valueOf((numberInit(pVal)).shortValue());
526 } else if (pType.equals(Integer.class) || pType.equals(Integer.TYPE)) {
527 param[0] = Integer.valueOf((numberInit(pVal)).intValue());
528 } else if (pType.equals(Long.class) || pType.equals(Long.TYPE)) {
529 param[0] = Long.valueOf((numberInit(pVal)).longValue());
530 } else if (pType.equals(Float.class) || pType.equals(Float.TYPE)) {
531 param[0] = Float.valueOf((numberInit(pVal)).floatValue());
532 } else if (pType.equals(Double.class) || pType.equals(Double.TYPE)) {
533 param[0] = Double.valueOf((numberInit(pVal)).doubleValue());
534 } else if (pType.equals(String.class)) {
535 param[0] = pVal;
536 } else if (pType.equals(BigDecimal.class)) {
537 param[0] = numberInit(pVal);
538 } else {
539 // try first to interpret pVal as class name...
540 try {
541 Class clazz = Class.forName(pVal);
542 param[0] = clazz.newInstance();
543 return param;
544 } catch (ClassNotFoundException cnfe) {
545 cnfe.printStackTrace();
546 } catch (InstantiationException iex) {
547 iex.printStackTrace();
548 } catch (IllegalAccessException iax) {
549 iax.printStackTrace();
550 }
551
552 // now try if the requested object can be instantiated using the
553 // given parameter value...
554 try {
555 Constructor constructor = pType.getConstructor(pVal.getClass());
556 param[0] = constructor.newInstance(pVal);
557 } catch (Exception ex) {
558 ex.printStackTrace();
559 param = null;
560 }
561 }
562
563 return param;
564 }
565
566 private void invokeSetter(final Object object, final Properties properties)
567 throws ConfigException {
568 Class clazz = object.getClass();
569 Method[] methods = clazz.getMethods();
570 Enumeration en = properties.propertyNames();
571
572 while (en.hasMoreElements()) {
573 String an = (String) en.nextElement();
574 String cont = properties.getProperty(an);
575 String mName = "set" + an.toLowerCase();
576
577 for (int i = 0; i < methods.length; i++) {
578 if (methods[i].getName().equalsIgnoreCase(mName)) {
579 Class[] pTypes = methods[i].getParameterTypes();
580 if (pTypes.length != 1) {
581 continue;
582 }
583 Object[] param = createParam(pTypes[0], cont);
584 if (param == null) {
585 continue;
586 }
587 try {
588 methods[i].invoke(object, param);
589 } catch (IllegalAccessException iae) {
590 throw new ConfigException(iae.getMessage());
591 } catch (InvocationTargetException itex) {
592 throw new ConfigException(itex.getMessage());
593 }
594 }
595 }
596 }
597 }
598
599
600 /**
601 * Configures the given object.
602 *
603 * @param object
604 * the object to be configured
605 * @throws org.molwind.util.ConfigException
606 * is thrown upon config error
607 */
608 public void setup(final Configurable object)
609 throws ConfigException {
610 if (!configStack.isEmpty()) {
611 Properties properties = (Properties) configStack.pop();
612 invokeSetter(object, properties);
613 }
614
615 }
616
617 /**
618 *
619 *@return
620 * returns an array of available worlds
621 *
622 */
623 public String[] getWorldNames() {
624
625 ArrayList<String> result = new ArrayList<String>();
626 for(WorldLocator w:getWorldLocators()){
627 for(String s:w.getWorldNames()){
628 result.add(s);
629 }
630
631 }
632 String[] resultArray = new String[result.size()];
633 int i = 0;
634 for(String h:result){
635 resultArray[i]=h;
636 i++;
637 }
638
639 return resultArray;
640
641
642 }
643
644
645
646
647
648
649
650 /**
651 * Reads the config.txt file.
652 *
653 * @param path
654 * path to the config file
655 */
656 // public void initialise(String path){
657 // confPath=path;
658 // TextIO rt = new TextIO();
659 // //System.out.println(confPath);
660 // al=rt.readFile(confPath);
661 //
662 // for (int i=0; i<al.size(); i++){
663 // if (al.get(i).equals("<WorldCount>")){
664 // worldCount=Integer.valueOf(al.get(i+1));
665 // }
666 // if (al.get(i).equals("<WorldNames>")){
667 // for(int i1=0; i1<worldCount; i1++){
668 // //names.put(al.get(i+i1+1),i1);
669 // //names2.put(i1, al.get(i+i1+1));
670 // String time=String.valueOf(
671 // System.currentTimeMillis() / 1000);
672 // time=time.substring(time.length()-5, time.length()-1);
673 // time = time+String.valueOf(i1);
674 // System.out.println(i1);
675 // names.put(time,i1);
676 // names2.put(i1, time);
677 // }
678 // }
679 // if (al.get(i).equals("<TargetFileURL>")){
680 // for(int i1=0; i1<worldCount; i1++){
681 // url.add(al.get(i+i1+1));
682 // }
683 //
684 // }
685 // if (al.get(i).equals("<inermediateLayers>")){
686 // if (Integer.valueOf(al.get(i+1))==1){
687 // intermediateLayer=true;
688 // }
689 // }
690 // if (al.get(i).equals("<ServerURL>")){
691 // server=al.get(i+1);
692 // }
693 // if (al.get(i).equals("<ImagesXMLFileURL>")){
694 // urlXML.add(al.get(i+1));
695 // }
696 // if (al.get(i).equals("<Features>")){
697 // int more=1;
698 // int i1=1;
699 // while(more==1){
700 // String s=al.get(i+i1);
701 // if (s.equals("</Features>")){
702 // more=0;
703 // }
704 // else if(s.startsWith("//")){
705 // i1=i1-1;
706 // }
707 // else{
708 // features.add(s);
709 // }
710 // i1++;
711 // }
712 // }
713 //
714 // }
715 // }
716
717 /**
718 * Re-reads the config file and updates the provided information.
719 */
720 // public void update(){
721 // TextIO rt = new TextIO();
722 // al=rt.readFile(confPath);
723 // int wcNew=worldCount;
724 // for (int i=0; i<al.size(); i++){
725 // if (al.get(i).equals("<WorldCount>")){
726 // wcNew=Integer.valueOf(al.get(i+1));
727 // }
728 // if (al.get(i).equals("<WorldNames>")){
729 // for(int i1=worldCount; i1<wcNew; i1++){
730 // //names.put(al.get(i+i1+1),i1);
731 // //names2.put(i1, al.get(i+i1+1));
732 // String time=String.valueOf(
733 // System.currentTimeMillis() / 1000);
734 // time=time.substring(time.length()-5, time.length()-1);
735 // time = time+String.valueOf(i1);
736 // System.out.println(i1);
737 // names.put(time,i1);
738 // names2.put(i1, time);
739 // }
740 // }
741 // }
742 // worldCount=wcNew;
743 //
744 // }
745
746 /**
747 * Returns the features which should be displayed
748 *
749 * @return
750 * the features which should be displayed
751 */
752 // public static ArrayList getFeatureList(){
753 // return features;
754 // }
755
756 /**
757 * Returns the id of a dataset with a given name
758 *
759 * @param s
760 * name of the dataset
761 * @return
762 * the id of a dataset with a given name
763 */
764 // public static int getWorldNumber(String s){
765 // return names.get(s);
766 // }
767
768 /**
769 * returns the name of a dataset with a given id
770 *
771 * @param i
772 * id of the dataset
773 * @return
774 * name of the dataset
775 */
776 // public static String getWorldName(int i){
777 // return names2.get(i);
778 // }
779
780 /**
781 * returns the number of different datasets
782 *
783 * @return
784 * number of different datasets
785 */
786 // public static int getWorldCount(){
787 // return worldCount;
788 // }
789
790 /**
791 * returns the path of the sd-file of a dataset-id
792 *
793 * @param i
794 * id of a dataset
795 * @return
796 * path of the sd-file of a dataset-id
797 */
798 // public static String sdfLocation(int i){
799 // return url.get(i);
800 // }
801
802 /**
803 * returns the zoomfactor of a dataset
804 *
805 * @param i
806 * id of a dataset
807 * @return
808 * returns the zoomfactor
809 */
810 // public static double getZoom(int i) {
811 // return zoom;
812 // }
813
814 /**
815 * returns the maximal number of molecules of level 0
816 *
817 * @param i
818 * id of a dataset
819 * @return
820 * maximal number of molecules of level 0
821 */
822 // public static int getMolsPerLevel(int i){
823 // return molsPerLevel;
824 // }
825
826 /**
827 * returns wheter intermadiate layers should be generated or not
828 *
829 * @param i
830 * id of a dataset
831 * @return
832 * true if intermediate layers should be generated, false if not.
833 */
834 // public static boolean getIntermediateLayer(int i){
835 // return intermediateLayer;
836 // }
837
838 /**
839 * returns the url of the server
840 *
841 * @return
842 * url of the server
843 */
844 // public static String getServerUrl(){
845 // return server;
846 // }
847
848 // public static void setStarted(int i){
849 // isStarted.put(i, 1);
850 // }
851
852 // public static int getStarted(int i){
853 // if (isStarted.containsKey(i)){
854 // return 1;
855 // }
856 // else{
857 // return 0;
858 // }
859 // }
860
861 }