001: /*
002: License $Id: JoServletService.java,v 1.9 2004/04/16 01:30:04 hendriks73 Exp $
003:
004: Copyright (c) 2001-2005 tagtraum industries.
005:
006: LGPL
007: ====
008:
009: jo! is free software; you can redistribute it and/or
010: modify it under the terms of the GNU Lesser General Public
011: License as published by the Free Software Foundation; either
012: version 2.1 of the License, or (at your option) any later version.
013:
014: jo! is distributed in the hope that it will be useful,
015: but WITHOUT ANY WARRANTY; without even the implied warranty of
016: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: Lesser General Public License for more details.
018:
019: You should have received a copy of the GNU Lesser General Public
020: License along with this library; if not, write to the Free Software
021: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022:
023: For LGPL see <http://www.fsf.org/copyleft/lesser.txt>
024:
025:
026: Sun license
027: ===========
028:
029: This release contains software by Sun Microsystems. Therefore
030: the following conditions have to be met, too. They apply to the
031: files
032:
033: - lib/mail.jar
034: - lib/activation.jar
035: - lib/jsse.jar
036: - lib/jcert.jar
037: - lib/jaxp.jar
038: - lib/crimson.jar
039: - lib/servlet.jar
040: - lib/jnet.jar
041: - lib/jaas.jar
042: - lib/jaasmod.jar
043:
044: contained in this release.
045:
046: a. Licensee may not modify the Java Platform
047: Interface (JPI, identified as classes contained within the javax
048: package or any subpackages of the javax package), by creating additional
049: classes within the JPI or otherwise causing the addition to or modification
050: of the classes in the JPI. In the event that Licensee creates any
051: Java-related API and distribute such API to others for applet or
052: application development, you must promptly publish broadly, an accurate
053: specification for such API for free use by all developers of Java-based
054: software.
055:
056: b. Software is confidential copyrighted information of Sun and
057: title to all copies is retained by Sun and/or its licensors. Licensee
058: shall not modify, decompile, disassemble, decrypt, extract, or otherwise
059: reverse engineer Software. Software may not be leased, assigned, or
060: sublicensed, in whole or in part. Software is not designed or intended
061: for use in on-line control of aircraft, air traffic, aircraft navigation
062: or aircraft communications; or in the design, construction, operation or
063: maintenance of any nuclear facility. Licensee warrants that it will not
064: use or redistribute the Software for such purposes.
065:
066: c. Software is provided "AS IS," without a warranty
067: of any kind. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES,
068: INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
069: PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
070:
071: d. This License is effective until terminated. Licensee may
072: terminate this License at any time by destroying all copies of Software.
073: This License will terminate immediately without notice from Sun if Licensee
074: fails to comply with any provision of this License. Upon such termination,
075: Licensee must destroy all copies of Software.
076:
077: e. Software, including technical data, is subject to U.S.
078: export control laws, including the U.S. Export Administration Act and its
079: associated regulations, and may be subject to export or import regulations
080: in other countries. Licensee agrees to comply strictly with all such
081: regulations and acknowledges that it has the responsibility to obtain
082: licenses to export, re-export, or import Software. Software may not be
083: downloaded, or otherwise exported or re-exported (i) into, or to a national
084: or resident of, Cuba, Iraq, Iran, North Korea, Libya, Sudan, Syria or any
085: country to which the U.S. has embargoed goods; or (ii) to anyone on the
086: U.S. Treasury Department's list of Specially Designated Nations or the U.S.
087: Commerce Department's Table of Denial Orders.
088:
089:
090: Feedback
091: ========
092:
093: We encourage your feedback and suggestions and want to use your feedback to
094: improve the Software. Send all such feedback to:
095: <feedback@tagtraum.com>
096:
097: For more information on tagtraum industries and jo!
098: please see <http://www.tagtraum.com/>.
099:
100:
101: */
102: package com.tagtraum.jo;
103:
104: import com.tagtraum.framework.http.C_Http;
105: import com.tagtraum.framework.http.StatusCodes;
106: import com.tagtraum.framework.log.C_Log;
107: import com.tagtraum.framework.log.Log;
108: import com.tagtraum.framework.recycler.Recycler;
109: import com.tagtraum.framework.server.ServerException;
110: import com.tagtraum.framework.server.Service;
111: import com.tagtraum.framework.util.FileCache;
112: import com.tagtraum.framework.util.URLDecoder;
113: import com.tagtraum.jo.builder.I_JoServiceBuilder;
114: import com.tagtraum.jo.security.I_JoAccessController;
115: import com.tagtraum.jo.security.I_JoAuthentificator;
116: import com.tagtraum.jo.security.I_JoRoleManager;
117: import com.tagtraum.perf.util.FastReadSyncMap;
118:
119: import java.io.File;
120: import java.io.IOException;
121: import java.net.URL;
122: import java.util.*;
123:
124: /**
125: * One of the main classes of the server. Each JoServletService has multiple
126: * virtual {@link I_JoHost}s. The hosts in turn have multiple {@link I_JoServletContextPeer}s
127: * that in turn have multiple {@link I_JoServletModel}s.
128: *
129: * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
130: * @version 1.1beta1 $Id: JoServletService.java,v 1.9 2004/04/16 01:30:04 hendriks73 Exp $
131: */
132: public class JoServletService extends Service implements
133: I_JoServletService, C_Jo {
134:
135: /**
136: * Source-Version.
137: */
138: public static String vcid = "$Id: JoServletService.java,v 1.9 2004/04/16 01:30:04 hendriks73 Exp $";
139: private static ResourceBundle localStrings = ResourceBundle
140: .getBundle("com.tagtraum.jo.localStrings");
141:
142: /**
143: * Build date.
144: */
145: public static String buildDate = "$Date: 2004/04/16 01:30:04 $";
146:
147: /**
148: * RoleManager.
149: */
150: private I_JoRoleManager roleManager;
151:
152: /**
153: * AccessController.
154: */
155: private I_JoAccessController accessController;
156:
157: /**
158: * Authentificator.
159: */
160: private Hashtable authentificators;
161:
162: /**
163: * Configuration URL.
164: */
165: private URL configURL;
166:
167: /**
168: * Jo home.
169: */
170: private File joHome;
171:
172: // ugly hack... that allows us to set joHome before starting things...
173: private static File _joHome;
174:
175: /**
176: * Keep-Alive time.
177: */
178: private int keepAliveTime;
179:
180: /**
181: * Max requests per connection.
182: */
183: private int maxRequests;
184:
185: /**
186: * The FileCache
187: */
188: private FileCache fileCache;
189:
190: /**
191: * Datastructure with Hosts.
192: */
193: private Map hosts;
194:
195: /**
196: * Mime types.
197: */
198: private HashMap mimeTypes;
199:
200: /**
201: * Watchdog timer for config reload.
202: */
203: private Timer timer;
204:
205: /**
206: * Builder.
207: */
208: private I_JoServiceBuilder builder;
209:
210: private List requestInterceptors;
211:
212: /**
213: * Constructor.
214: */
215: public JoServletService() {
216: super ();
217: mimeTypes = new HashMap();
218: try {
219: hosts = new FastReadSyncMap(new HashMap());
220: } catch (CloneNotSupportedException cnse) {
221: cnse.printStackTrace();
222: throw new RuntimeException(cnse.toString());
223: }
224: maxRequests = C_DefaultMaxRequests;
225: keepAliveTime = C_DefaultKeepAlive;
226: authentificators = new Hashtable();
227: fileCache = new FileCache();
228: fileCache.setMaxEntrySize(100 * 1024); // 100 kb
229: requestInterceptors = new ArrayList();
230: }
231:
232: /**
233: * Sets the name of the service and the name of the named {@link Recycler}.
234: *
235: * @param aName
236: *
237: * @see Recycler#getNamedRecycler(String)
238: */
239: public void setName(String aName) {
240: super .setName(aName);
241: myFactory = Recycler.getNamedRecycler(aName);
242: }
243:
244: public void addRequestInterceptor(
245: RequestInterceptor requestInterceptor) {
246: requestInterceptors.add(requestInterceptor);
247: }
248:
249: public boolean removeRequestInterceptor(
250: RequestInterceptor requestInterceptor) {
251: return requestInterceptors.remove(requestInterceptor);
252: }
253:
254: public Iterator requestInterceptors(
255: RequestInterceptor requestInterceptor) {
256: return requestInterceptors.iterator();
257: }
258:
259: /**
260: * Unmodifiable list
261: *
262: * @return unmodifiable list
263: */
264: public List getRequestInterceptorList() {
265: return Collections.unmodifiableList(requestInterceptors);
266: }
267:
268: /**
269: * Returns an Iterator over the registered hosts.
270: *
271: * @return iterator over hosts
272: */
273: public Iterator hosts() {
274: HashSet hostSet = new HashSet();
275: hostSet.addAll(hosts.values());
276: return hostSet.iterator();
277: }
278:
279: /**
280: * Adds a host to this service.
281: *
282: * @param aHost a Host
283: */
284: public void addHost(I_JoHost aHost) {
285: String[] theHostnames = aHost.getHostnames();
286: for (int i = 0; i < theHostnames.length; i++) {
287: hosts.put(theHostnames[i], aHost);
288: }
289: }
290:
291: /**
292: * Removes a Host.
293: *
294: * @param aHost host to remove
295: */
296: public void removeHost(I_JoHost aHost) {
297: String[] theHostnames = aHost.getHostnames();
298: for (int i = 0; i < theHostnames.length; i++) {
299: hosts.remove(theHostnames[i]);
300: }
301: aHost.destroy();
302: }
303:
304: /**
305: * Returns a host for a hostname. If no host is found the host with
306: * the jokername "*" is returned.
307: *
308: * @param aHostname a host name
309: * @return matching host, joker host or null
310: */
311: public I_JoHost getHost(String aHostname) {
312: if (aHostname == null) {
313: aHostname = "*";
314: }
315: I_JoHost theHost = (I_JoHost) hosts.get(aHostname);
316: if (theHost == null) {
317: // try jokerhost
318: theHost = (I_JoHost) hosts.get("*");
319: }
320: return theHost;
321: }
322:
323: /**
324: * Stops the engine. First the reloadtimer is stopped, then the service,
325: * then {@link #destroy()} is called.
326: */
327: public synchronized void stop() throws ServerException {
328: if (timer != null)
329: timer.stop();
330: timer = null;
331: super .stop();
332: try {
333: destroy();
334: } finally {
335: hosts.clear();
336: }
337: }
338:
339: /**
340: * Starts the engine.
341: *
342: * @exception ServerException if it is not possible to
343: * start this <code>Service</code>
344: */
345: public synchronized void start() throws ServerException {
346: try {
347: super .start();
348: } finally {
349: timer = new Timer(1000);
350: timer.start();
351: }
352: }
353:
354: /**
355: * Destroys all hosts.
356: */
357: public synchronized void destroy() {
358: Iterator i = hosts.values().iterator();
359: while (i.hasNext()) {
360: ((I_JoHost) i.next()).destroy();
361: }
362: Iterator ri = requestInterceptors.iterator();
363: while (ri.hasNext()) {
364: ((RequestInterceptor) ri.next()).destroy();
365: }
366: }
367:
368: /**
369: * Returns the value of an attribute.
370: *
371: * @param name name
372: * @return value
373: */
374: public Object getAttribute(String name) {
375: return super .getAttribute(name);
376: }
377:
378: /**
379: * Return an iterator over all registered mime types.
380: *
381: * @return Iterator
382: */
383: public synchronized Iterator getMimeTypes() {
384: return mimeTypes.keySet().iterator();
385: }
386:
387: /**
388: * Returns the mime type of a file or null if it is unknown.
389: *
390: * @param file file name
391: */
392: public synchronized String getMimeType(String file) {
393: int i = file.lastIndexOf(".");
394: if (i == -1 || i == file.length() - 1) {
395: return null;
396: }
397: return (String) mimeTypes.get(file.substring(i + 1));
398: }
399:
400: /**
401: * Adds a mime type.
402: *
403: * @param aSuffix suffix
404: * @param aMimeType mime type
405: */
406: public synchronized void addMimeType(String aSuffix,
407: String aMimeType) {
408: mimeTypes.put(aSuffix, aMimeType);
409: }
410:
411: /**
412: * Removes a suffix.
413: *
414: * @param aSuffix file suffix
415: */
416: public synchronized void removeMimeType(String aSuffix) {
417: mimeTypes.remove(aSuffix);
418: }
419:
420: /**
421: * Returns a host for a request.
422: *
423: * @param aRequest a {@link I_JoServletRequest}
424: * @return matching host object or <code>null</code>
425: */
426: public I_JoHost getHost(I_JoServletRequest aRequest) {
427: String theHostname = aRequest.getHeader(C_Http.C_HTTP_Host);
428: if (theHostname != null) {
429: int colon;
430: if ((colon = theHostname.indexOf(':')) != -1) {
431: theHostname = theHostname.substring(0, colon);
432: }
433: }
434: return getHost(theHostname);
435: }
436:
437: /**
438: * Sets the time a connection should be kept alive.
439: *
440: * @param aTime time in seconds this connection should be kept alive.
441: */
442: public void setKeepAlive(int aTime) {
443: if (aTime < 0) {
444: throw new IllegalArgumentException(localStrings
445: .getString("keep_alive_must_be_positive"));
446: }
447: keepAliveTime = aTime;
448: }
449:
450: /**
451: * Returns the time a connection should be kept alive.
452: *
453: * @return time in seconds this connection should be kept alive.
454: */
455: public int getKeepAlive() {
456: return keepAliveTime;
457: }
458:
459: /**
460: * Sets the number of requests that can be sent over a
461: * persistent connection.
462: *
463: * @param aMaxRequests number of requests that can be sent over
464: * a persistent connection.
465: */
466: public void setMaxRequests(int aMaxRequests) {
467: maxRequests = aMaxRequests;
468: }
469:
470: /**
471: * Returns the number of request that can be sent over a
472: * persistent connection.
473: *
474: * @return number of requests that can be sent over
475: * a persistent connection.
476: */
477: public int getMaxRequests() {
478: return maxRequests;
479: }
480:
481: /**
482: * Returns an errorpage.
483: *
484: * @param aStatusCode Statuscode
485: * @param aMessage message
486: * @return message page
487: */
488: public String getErrorPage(int aStatusCode, String aMessage) {
489: StringBuffer sb = new StringBuffer();
490:
491: sb.append("<h2>");
492: sb.append(aStatusCode);
493: sb.append(' ');
494: sb.append(StatusCodes.get(aStatusCode));
495: sb.append("</h2>");
496:
497: if (aMessage != null) {
498: sb.append("<pre>");
499: sb.append(aMessage);
500: sb.append("</pre>");
501: }
502:
503: return sb.toString();
504: }
505:
506: /**
507: * Returns an errorpage.
508: *
509: * @param aStatusCode Statuscode
510: * @param aThrowable Throwable
511: * @return message page
512: */
513: public String getErrorPage(int aStatusCode, Throwable aThrowable) {
514: return getErrorPage(aStatusCode, Log.getStackTrace(aThrowable));
515: }
516:
517: /**
518: * Returns a {@link I_JoAuthentificator} for an auth-method or
519: * <code>null</code> if there is no matching one.
520: * Auth-method names are caseinsensitve.
521: *
522: * @return an authentificator or <code>null</code>
523: */
524: public I_JoAuthentificator getAuthentificator(String aAuthMethod) {
525: I_JoAuthentificator theAuthentificator = (I_JoAuthentificator) authentificators
526: .get(aAuthMethod);
527:
528: if (theAuthentificator == null) {
529: theAuthentificator = (I_JoAuthentificator) authentificators
530: .get(aAuthMethod.toUpperCase());
531: }
532:
533: return theAuthentificator;
534: }
535:
536: /**
537: * Adds an {@link I_JoAuthentificator} for an auth-method.
538: *
539: * @param aAuthMethod an authentification method
540: * @param anAuthentificator an authentificator
541: */
542: public void addAuthentificator(String aAuthMethod,
543: I_JoAuthentificator anAuthentificator) {
544: authentificators.put(aAuthMethod.toUpperCase(),
545: anAuthentificator);
546: anAuthentificator.setService(this );
547: }
548:
549: /**
550: * Removes an {@link I_JoAuthentificator} for an auth-method.
551: *
552: * @param aAuthMethod an authentification method
553: */
554: public void removeAuthentificator(String aAuthMethod) {
555: authentificators.remove(aAuthMethod.toUpperCase());
556: }
557:
558: /**
559: * Returns the active {@link I_JoAccessController} for this service.
560: *
561: * @return an accesscontroller
562: */
563: public I_JoAccessController getAccessController() {
564: return accessController;
565: }
566:
567: /**
568: * Returns the {@link I_JoAccessController} for this service.
569: *
570: * @param anAccessController an accesscontroller
571: */
572: public void setAccessController(
573: I_JoAccessController anAccessController) {
574: accessController = anAccessController;
575:
576: accessController.setService(this );
577: }
578:
579: /**
580: * Returns the {@link I_JoRoleManager} for this service.
581: *
582: * @return a role manager
583: */
584: public I_JoRoleManager getRoleManager() {
585: return roleManager;
586: }
587:
588: /**
589: * Returns the {@link I_JoRoleManager} for this service.
590: *
591: * @param aRoleManager a RoleManager
592: */
593: public void setRoleManager(I_JoRoleManager aRoleManager) {
594: roleManager = aRoleManager;
595:
596: roleManager.setService(this );
597: }
598:
599: /**
600: * Sets the URL where all configuration files are located.
601: *
602: * @param aConfigURL the URL where all configuration files are located
603: */
604: public void setConfigURL(URL aConfigURL) {
605: configURL = aConfigURL;
606: }
607:
608: /**
609: * Returns the URL where all configuration files are located.
610: *
611: * @return the URL where all configuration files are located
612: */
613: public URL getConfigURL() {
614: return configURL;
615: }
616:
617: /**
618: * Base directory defined in JO_HOME. If the environment
619: * variable is not set, the current directory is used.
620: * JoHome will be a canonical path.
621: */
622: public File getJoHome() {
623: if (joHome == null) {
624: File _joHome = _getJoHome();
625: try {
626: joHome = _joHome.getCanonicalFile();
627: } catch (IOException ioe) {
628: joHome = _joHome;
629: if (Log.getLog(getName()).isLog(C_Log.ERROR)) {
630: Log
631: .getLog(getName())
632: .log(
633: localStrings
634: .getString("canonical_jo_home_failed")
635: + joHome, C_Log.ERROR);
636: }
637: }
638: if (Log.getLog(getName()).isLog(C_Log.MODULE)) {
639: Log.getLog(getName()).log("JO_HOME = " + joHome,
640: C_Log.MODULE);
641: }
642: }
643: return joHome;
644: }
645:
646: /**
647: * Overrides the system property JO_HOME.
648: */
649: public static void _setJoHome(File joHome) {
650: _joHome = joHome;
651: }
652:
653: public static File _getJoHome() {
654: if (_joHome == null) {
655: String prop = System.getProperty(JO_HOME);
656: if (prop != null) {
657: _joHome = new File(System.getProperty(JO_HOME));
658: } else {
659: try {
660: // hack that assumes that com.tagtraum.jo.JoServletService
661: // is in JO_HOME/lib/jo.jar
662: String className = JoServletService.class.getName()
663: .replace('.', '/')
664: + ".class";
665: URL url = JoServletService.class.getClassLoader()
666: .getResource(className);
667: String utilUrlString = url.toString();
668: int idx = utilUrlString.indexOf("/lib/jo.jar!");
669: if (idx != -1) {
670: url = new URL(url.toString().substring(
671: "jar:".length(), idx + "/lib".length()));
672: _joHome = new File(URLDecoder.decode(url
673: .getFile())).getParentFile();
674: } else {
675: // hack failed, so we go for the current dir.
676: _joHome = new File(System
677: .getProperty("user.dir"));
678: }
679: } catch (ClassNotFoundException cnfe) {
680: // should not be possible
681: cnfe.printStackTrace();
682: } catch (Exception e) {
683: // should not be possible
684: e.printStackTrace();
685: }
686: }
687: }
688: return _joHome;
689: }
690:
691: /**
692: * Returns the FileCache.
693: *
694: * @return the FileCache
695: */
696: public FileCache getFileCache() {
697: return fileCache;
698: }
699:
700: /**
701: * Sets the Builder for this service.
702: */
703: public void setBuilder(I_JoServiceBuilder builder) {
704: this .builder = builder;
705: }
706:
707: /**
708: * Gets the Builder for this service.
709: */
710: public I_JoServiceBuilder getBuilder() {
711: return builder;
712: }
713:
714: /**
715: * Rebuilds the service or its components if necessary.
716: */
717: public void rebuild() {
718: if (getBuilder().needsRebuild()) {
719: if (Log.getLog(getName()).isLog(C_Log.MODULE)) {
720: Log.getLog(getName()).log(
721: localStrings.getString("rebuilding_service")
722: + this , C_Log.MODULE);
723: }
724: try {
725: getBuilder().rebuild(this );
726: } catch (Exception e) {
727: if (Log.getLog(getName()).isLog(C_Log.ERROR)) {
728: Log
729: .getLog(getName())
730: .log(
731: localStrings
732: .getString("rebuilding_service_failed")
733: + this , C_Log.ERROR);
734: Log.getLog(getName()).log(e, C_Log.ERROR);
735: }
736: }
737: }
738: // if the service does not need to be rebuilt, maybe some
739: // host needs rebuilding...
740: else {
741: Iterator it = hosts.values().iterator();
742: while (it.hasNext()) {
743: I_JoHost host = (I_JoHost) it.next();
744: host.rebuild();
745: }
746: }
747: }
748:
749: /**
750: * Is called by the timer.
751: */
752: public void handleTimer() {
753: rebuild();
754: }
755:
756: // TODO: replace this with util.timer?
757: private class Timer implements Runnable {
758: private boolean running;
759: private long interval;
760: private Thread thread;
761:
762: public Timer(long interval) {
763: this .interval = interval;
764: }
765:
766: public void start() {
767: thread = new Thread(this , "JoService Reload Timer");
768: running = true;
769: thread.start();
770: }
771:
772: public synchronized void run() {
773: while (running) {
774: try {
775: wait(interval);
776: try {
777: handleTimer();
778: } catch (Throwable t) {
779: Log.getLog(getName()).log(t, C_Log.ERROR);
780: }
781: } catch (InterruptedException ie) {
782: // ignore.
783: }
784: }
785: }
786:
787: public synchronized void stop() {
788: running = false;
789: thread.interrupt();
790: thread = null;
791: notify();
792: }
793: }
794:
795: }
|