001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999-2005 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * Initial developer(s): Miroslav Halas based on Catalina service
022: * --------------------------------------------------------------------------
023: * $Id: JettyJWebContainerServiceImpl.java 7243 2005-08-12 17:58:34Z benoitf $
024: * --------------------------------------------------------------------------
025: */package org.objectweb.jonas.web.jetty50;
026:
027: import java.io.File;
028: import java.io.IOException;
029: import java.net.URL;
030: import java.net.URLClassLoader;
031: import java.util.Map;
032: import java.util.StringTokenizer;
033: import java.util.Vector;
034:
035: import javax.management.MalformedObjectNameException;
036: import javax.management.ObjectName;
037: import javax.naming.Context;
038: import javax.naming.NamingException;
039:
040: import org.objectweb.util.monolog.api.BasicLevel;
041:
042: import org.objectweb.jonas.jmx.JmxService;
043: import org.objectweb.jonas.jmx.JonasObjectName;
044: import org.objectweb.jonas.service.ServiceException;
045: import org.objectweb.jonas.service.ServiceManager;
046: import org.objectweb.jonas.web.AbsJWebContainerServiceImpl;
047: import org.objectweb.jonas.web.JWebContainerServiceException;
048:
049: import org.mortbay.http.HttpContext;
050: import org.mortbay.http.HttpListener;
051: import org.mortbay.jetty.Server;
052: import org.mortbay.jetty.servlet.WebApplicationContext;
053:
054: /**
055: * This class provides an implementation of the Jetty service (as web container
056: * service).
057: * @author Miroslav Halas (initial developer)
058: * @author Florent Benoit
059: */
060: public class JettyJWebContainerServiceImpl extends
061: AbsJWebContainerServiceImpl {
062:
063: /**
064: * Name of the default web.xml file
065: */
066: private static final String JETTY_DEFAULT_WEB_XML_FILE = AbsJWebContainerServiceImpl.JONAS_BASE
067: + File.separator
068: + "conf"
069: + File.separator
070: + "jetty5-webdefault.xml";
071:
072: /**
073: * Configuration used to configure Jetty.
074: */
075: private static String config = null;
076:
077: /**
078: * Name of the variable specifying configuration file for Jetty
079: */
080: private static final String JETTY_CONFIG = "config";
081:
082: /**
083: * Our own instance of Jetty server.
084: */
085: private Server jettyServer = null;
086:
087: /**
088: * Initialize the Jetty service.
089: * @param ctx the configuration context of the service.
090: * @throws ServiceException if the initialization failed.
091: */
092: protected void doInit(javax.naming.Context ctx)
093: throws ServiceException {
094:
095: super .doInit(ctx);
096:
097: String strJettyHome = System.getProperty("jetty.home");
098: if (strJettyHome != null) {
099: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
100: getLogger().log(BasicLevel.DEBUG, "");
101: }
102:
103: try {
104: config = (String) ctx.lookup(JETTY_CONFIG);
105: } catch (NamingException neExc) {
106: // by default we take jetty5.xml under JONAS_BASE/conf
107: String jonasbase = System.getProperties().getProperty(
108: "jonas.base");
109: config = jonasbase + "/conf/jetty5.xml";
110: }
111: getLogger().log(BasicLevel.LEVEL_DEBUG,
112: "using configuration file " + config);
113:
114: jettyServer = new Server();
115: }
116:
117: }
118:
119: /**
120: * Start the Jetty service in a new thread
121: * @throws ServiceException if the startup failed.
122: */
123: public void doStart() throws ServiceException {
124: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
125: getLogger().log(BasicLevel.DEBUG, "");
126: }
127:
128: if (jettyServer != null) {
129: JmxService srvcJMX = null;
130:
131: // Start Jetty directly and just pass it to JMX if available
132: if (config != null) {
133: try {
134: jettyServer.configure(config);
135: jettyServer.start();
136: } catch (Exception eExc) {
137: getLogger().log(
138: BasicLevel.LEVEL_ERROR,
139: "error has occured while starting Jetty server using configuration file "
140: + config, eExc);
141: }
142: }
143:
144: ServiceManager sm = null;
145: try {
146: sm = ServiceManager.getInstance();
147: } catch (Exception e) {
148: String err = "Cannot get ServiceManager instance.";
149: getLogger().log(BasicLevel.ERROR, err);
150: throw new ServiceException(err, e);
151: }
152:
153: try {
154: srvcJMX = (JmxService) sm.getJmxService();
155: if (srvcJMX != null) {
156: // Register JettyService MBean : JettyService
157: Object obj;
158: ObjectName objname;
159: // Once we register this bean, it will start Jetty using
160: // given configuration
161: // file. That's why we have to do it in run() instead in
162: // doStart()
163: obj = new JettyJonasServerMBean(jettyServer);
164: objname = JonasObjectName.wwwService();
165: srvcJMX.getJmxServer().registerMBean(obj, objname);
166: }
167: } catch (ServiceException seExc) {
168: // Jmx Service not available, do nothing
169: getLogger()
170: .log(
171: BasicLevel.LEVEL_DEBUG,
172: "cannot start Jetty server using configuration file "
173: + config
174: + " using JMX. Will start without JMX.",
175: seExc);
176: } catch (Exception eExc) {
177: getLogger().log(
178: BasicLevel.LEVEL_ERROR,
179: "cannot start Jetty server using configuration file "
180: + config, eExc);
181: throw new ServiceException(
182: "Cannot start Jetty server using configuration file "
183: + config, eExc);
184: }
185:
186: // ... and deploy wars of the jonas.properties
187: super .doStart();
188: } else {
189: throw new ServiceException("Cannot start Jetty server.");
190: }
191: }
192:
193: /**
194: * Stop the Jetty service.
195: * @throws ServiceException if the stop failed.
196: */
197: protected void doStop() throws ServiceException {
198: // Undeploy the wars ...
199: super .doStop();
200:
201: // ... and shut down embedded jetty
202: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
203: getLogger().log(BasicLevel.DEBUG, "");
204: }
205: if (isStarted()) {
206: JmxService srvcJMX = null;
207:
208: ServiceManager sm = null;
209: try {
210: sm = ServiceManager.getInstance();
211: } catch (Exception e) {
212: String err = "Cannot get ServiceManager instance.";
213: getLogger().log(BasicLevel.ERROR, err);
214: throw new ServiceException(err, e);
215: }
216:
217: try {
218: srvcJMX = (JmxService) sm.getJmxService();
219: if (srvcJMX != null) {
220: ObjectName objname;
221:
222: objname = new ObjectName(
223: "jonas:type=service,name=jetty");
224: srvcJMX.getJmxServer().unregisterMBean(objname);
225: jettyServer = null;
226: objname = new ObjectName(
227: "jonas:type=jetty,name=jettylog");
228: srvcJMX.getJmxServer().unregisterMBean(objname);
229: objname = new ObjectName(
230: "jonas:type=jetty,name=jettycode");
231: srvcJMX.getJmxServer().unregisterMBean(objname);
232: }
233: } catch (ServiceException seExc) {
234: getLogger().log(BasicLevel.LEVEL_ERROR,
235: "JMX Service not available", seExc);
236: } catch (Exception eExc) {
237: getLogger().log(BasicLevel.LEVEL_ERROR,
238: "Cannot stop Jetty server", eExc);
239: throw new ServiceException("Cannot stop Jetty server",
240: eExc);
241: }
242:
243: if (jettyServer != null) {
244: try {
245: jettyServer.stop();
246: jettyServer.destroy();
247: jettyServer = null;
248: } catch (Exception eExc) {
249: getLogger().log(
250: BasicLevel.LEVEL_ERROR,
251: "error has occured while stopping Jetty server using configuration file "
252: + config, eExc);
253: }
254: }
255: }
256: }
257:
258: /**
259: * Create the environment and delegate the operation to the implementation
260: * of the web container.
261: * @param ctx the context which contains the configuration in order to
262: * deploy a WAR.
263: * @throws JWebContainerServiceException if the registration of the WAR
264: * failed.
265: */
266: protected void doRegisterWar(Context ctx)
267: throws JWebContainerServiceException {
268: // Get the 5 parameters :
269: // - warURL is the URL of the war to register (required param).
270: // - contextRoot is the context root to which this application
271: // should be installed (must be unique) (required param).
272: // - hostName is the name of the host on which deploy the war
273: // (optional param taken into account only if no <context> element
274: // was declared in server.xml for this web application) .
275: // - java2DelegationModel the compliance to java2 delegation model
276: // - parentCL the war classloader of this war.
277: //URL warURL = null;
278: URL unpackedWarURL = null;
279: String contextRoot = null;
280: boolean java2DelegationModel = true;
281: try {
282: unpackedWarURL = (URL) ctx.lookup("unpackedWarURL");
283: contextRoot = (String) ctx.lookup("contextRoot");
284: Boolean bool = (Boolean) ctx.lookup("java2DelegationModel");
285: java2DelegationModel = bool.booleanValue();
286: } catch (NamingException e) {
287: String err = "Error while getting parameter from context param ";
288: getLogger().log(BasicLevel.ERROR, err + e.getMessage());
289: throw new JWebContainerServiceException(err, e);
290: }
291:
292: ClassLoader webClassLoader = null;
293: try {
294: webClassLoader = (ClassLoader) ctx.lookup("parentCL");
295: } catch (NamingException e) {
296: String err = "error while getting parameter from context param ";
297: getLogger().log(BasicLevel.ERROR, err + e.getMessage());
298: throw new JWebContainerServiceException(err, e);
299: }
300:
301: String hostName = null;
302: try {
303: hostName = (String) ctx.lookup("hostName");
304: } catch (NamingException e) {
305: hostName = "";
306: }
307:
308: String earAppName = null;
309: try {
310: earAppName = (String) ctx.lookup("earAppName");
311: } catch (NamingException e) {
312: // no ear case, so no ear application name
313: earAppName = null;
314: }
315:
316: // Install a new web application, whose web application archive is
317: // at the specified URL, into this container with the specified
318: // context root.
319: // A context root of "" (the empty string) should be used for the root
320: // application for this container. Otherwise, the context root must
321: // start with a slash.
322:
323: if (contextRoot.equals("/")) {
324: contextRoot = "";
325: } else if (contextRoot.equalsIgnoreCase("ROOT")) {
326: // Jetty uses ROOT.war and ROOT directory to as root context
327: contextRoot = "";
328: }
329:
330: // install the war.
331: File fWar = new File(unpackedWarURL.getFile());
332: String fileName = fWar.getAbsolutePath();
333:
334: if (jettyServer != null) {
335: try {
336: WebApplicationContext contextWebApp;
337:
338: if ((hostName == null) || (hostName.length() == 0)) {
339: // There is no host
340: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
341: getLogger().log(
342: BasicLevel.DEBUG,
343: "Jetty server installing web app "
344: + fileName + " and context "
345: + contextRoot);
346: }
347: contextWebApp = jettyServer.addWebApplication(
348: contextRoot, fileName);
349: } else {
350: // Host was specified
351: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
352: getLogger().log(
353: BasicLevel.DEBUG,
354: "Jetty server installing web app "
355: + fileName + " on host "
356: + hostName + " and context "
357: + contextRoot);
358: }
359: contextWebApp = jettyServer.addWebApplication(
360: hostName, contextRoot, fileName);
361:
362: }
363:
364: contextWebApp.setAttribute("J2EEDomainName",
365: getDomainName());
366: contextWebApp.setAttribute("J2EEServerName",
367: getJonasServerName());
368: contextWebApp.setAttribute("J2EEApplicationName",
369: earAppName);
370:
371: // Add default xml descriptor
372: File webDefaults = new File(JETTY_DEFAULT_WEB_XML_FILE);
373: if (webDefaults.exists()) {
374: contextWebApp.setDefaultsDescriptor(webDefaults
375: .toURL().toExternalForm());
376: } else {
377: getLogger()
378: .log(
379: BasicLevel.WARN,
380: "The file '"
381: + JETTY_DEFAULT_WEB_XML_FILE
382: + "' is not present. Check that your JONAS_BASE is up-to-date.");
383: }
384:
385: // Specifying the jsp class path used by jasper
386: contextWebApp.setAttribute(
387: "org.apache.catalina.jsp_classpath",
388: getJOnASClassPath(webClassLoader));
389:
390: // Set the parent class loader
391: contextWebApp.setParentClassLoader(webClassLoader);
392:
393: // Set this classloader to the Java2 compliant mode ?
394: contextWebApp
395: .setClassLoaderJava2Compliant(java2DelegationModel);
396:
397: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
398: getLogger().log(
399: BasicLevel.DEBUG,
400: "Webapp class loader java 2 delegation model set to "
401: + java2DelegationModel);
402:
403: getLogger()
404: .log(
405: BasicLevel.DEBUG,
406: "Jetty server starting web app "
407: + fileName);
408: }
409: contextWebApp.start();
410: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
411: getLogger().log(
412: BasicLevel.DEBUG,
413: "Jetty server is running web app "
414: + fileName);
415: }
416:
417: } catch (IOException ioeExc) {
418: String err = "Cannot install this web application "
419: + ioeExc;
420: getLogger().log(BasicLevel.ERROR, err);
421: throw new JWebContainerServiceException(err, ioeExc);
422: } catch (Exception eExc) {
423: String err = "Cannot start this web application "
424: + eExc;
425: getLogger().log(BasicLevel.ERROR, err);
426: throw new JWebContainerServiceException(err, eExc);
427: }
428: } else {
429: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
430: getLogger().log(
431: BasicLevel.DEBUG,
432: "No Jetty server to install web app "
433: + fileName);
434: }
435: }
436:
437: // TODO We need to have the J2EE WebModule MBean here, should add it to the Context
438: // Store WebModule ObjectName in Context
439: try {
440: ctx.rebind("WebModule", getDummyJSR77ObjectName(hostName,
441: contextRoot, earAppName));
442: } catch (Exception e) {
443: // NamingException or Mbean related Exception
444: // TODO i18n
445: String err = "Cannot rebind WebModule ObjectName in Context";
446: getLogger().log(BasicLevel.ERROR, err, e);
447: throw new JWebContainerServiceException(err, e);
448: }
449:
450: }
451:
452: /**
453: * Create a fake, JSR77 MBean ObjectName
454: * @param hostName host name
455: * @param contextRoot context root name
456: * @param earAppName application name
457: * @return a fake JSR77 WebModule ObjectName
458: * @throws MalformedObjectNameException if ObjectName incorrect
459: */
460: private ObjectName getDummyJSR77ObjectName(String hostName,
461: String contextRoot, String earAppName)
462: throws MalformedObjectNameException {
463: // jonas:j2eeType=WebModule,name=//localhost/,J2EEApplication=none,J2EEServer=jonas
464: return ObjectName.getInstance(getDomainName()
465: + ":j2eeType=WebModule,name=" + "/" + contextRoot
466: + ",J2EEApplication=" + earAppName + ",J2EEServer="
467: + getJonasServerName());
468: }
469:
470: /**
471: * Return the classpath which can be used for jsp compiling by Jasper. This
472: * classpath is extracted from the web classloader.
473: * @param webClassLoader the ClassLoader used for extract URLs.
474: * @return the jonas classpath which is useful for JSP compiling.
475: */
476: public String getJOnASClassPath(ClassLoader webClassLoader) {
477:
478: StringBuffer classpath = new StringBuffer();
479: int n = 0;
480: while (webClassLoader != null) {
481: if (!(webClassLoader instanceof URLClassLoader)) {
482: break;
483: }
484: URL[] repositories = ((URLClassLoader) webClassLoader)
485: .getURLs();
486: for (int i = 0; i < repositories.length; i++) {
487: String repository = repositories[i].toString();
488: if (repository.startsWith("file://")) {
489: repository = repository.substring("file://"
490: .length());
491: } else if (repository.startsWith("file:")) {
492: repository = repository.substring("file:".length());
493: } else {
494: continue;
495: }
496: if (repository == null) {
497: continue;
498: }
499: if (n > 0) {
500: classpath.append(File.pathSeparator);
501: }
502: classpath.append(repository);
503: n++;
504: }
505: webClassLoader = webClassLoader.getParent();
506: }
507:
508: return classpath.toString();
509: }
510:
511: /**
512: * Delegate the unregistration to the implementation of the web container.
513: * @param ctx the context which contains the configuration in order to
514: * undeploy a WAR.
515: * @throws JWebContainerServiceException if the unregistration failed.
516: */
517: protected void doUnRegisterWar(Context ctx)
518: throws JWebContainerServiceException {
519: // Get the 2 parameters :
520: // - contextRoot is the context root to be removed (required param).
521: // - hostName is the name of the host to remove the war (optional).
522: String contextRoot = null;
523: try {
524: contextRoot = (String) ctx.lookup("contextRoot");
525: } catch (NamingException e) {
526: String err = "Error while getting parameter from context param ";
527: getLogger().log(BasicLevel.ERROR, err + e.getMessage());
528: throw new JWebContainerServiceException(err, e);
529: }
530:
531: String hostName = null;
532: try {
533: hostName = (String) ctx.lookup("hostName");
534: } catch (NamingException e) {
535: hostName = "";
536: }
537: // A context root of "" (the empty string) should be used for the root
538: // application for this container. Otherwise, the context root must
539: // start with a slash.
540:
541: if (contextRoot.equals("/")) {
542: contextRoot = "";
543: } else if (contextRoot.equalsIgnoreCase("ROOT")) {
544: // Jetty uses ROOT.war and ROOT directory to as root context
545: contextRoot = "";
546: }
547:
548: if (jettyServer != null) {
549: HttpContext contextWebApp;
550:
551: if ((hostName == null) || (hostName.length() == 0)) {
552: // There is no host
553: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
554: getLogger().log(
555: BasicLevel.DEBUG,
556: "Jetty server looking upweb app "
557: + " from context " + contextRoot);
558: }
559: contextWebApp = jettyServer.getContext(contextRoot);
560: } else {
561: // Host was specified
562: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
563: getLogger().log(
564: BasicLevel.DEBUG,
565: "Jetty server looking up web app "
566: + " on host " + hostName
567: + " and context " + contextRoot);
568: }
569: contextWebApp = jettyServer.getContext(hostName,
570: contextRoot);
571: }
572:
573: if (contextWebApp != null) {
574: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
575: getLogger().log(
576: BasicLevel.DEBUG,
577: "Jetty server found and is stopping web app at context "
578: + contextRoot);
579: }
580: // Stop it gracefully
581: try {
582: contextWebApp.stop(true);
583: } catch (InterruptedException ieExc) {
584: getLogger()
585: .log(
586: BasicLevel.LEVEL_DEBUG,
587: "Jetty server encoutered exception while stopping web application ",
588: ieExc);
589: }
590: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
591: getLogger().log(
592: BasicLevel.DEBUG,
593: "Jetty server stopped and is removing web app at context "
594: + contextRoot);
595: }
596:
597: jettyServer.removeContext(contextWebApp);
598: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
599: getLogger().log(
600: BasicLevel.DEBUG,
601: "Jetty server removed and is destroying web app at context "
602: + contextRoot);
603: }
604: contextWebApp.destroy();
605:
606: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
607: getLogger().log(
608: BasicLevel.DEBUG,
609: "Jetty server unloaded web app at context "
610: + contextRoot);
611: }
612:
613: } else {
614: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
615: getLogger().log(
616: BasicLevel.DEBUG,
617: "Jetty server didn't find web app at context "
618: + contextRoot);
619: }
620: }
621:
622: } else {
623: if (getLogger().isLoggable(BasicLevel.DEBUG)) {
624: getLogger().log(
625: BasicLevel.DEBUG,
626: "No Jetty server to install web app at context "
627: + contextRoot);
628: }
629: }
630: }
631:
632: /**
633: * Update info of the serverName and serverVersion
634: */
635: protected void updateServerInfos() {
636: String infos = org.mortbay.http.Version.getImplVersion();
637:
638: StringTokenizer st = new StringTokenizer(infos, "/");
639: if (st.countTokens() != 2) {
640: setServerName(infos);
641: setServerVersion("");
642: } else {
643: setServerName(st.nextToken());
644: setServerVersion(st.nextToken());
645: }
646: }
647:
648: /**
649: * Return the Default host name of the web container.
650: * @return the Default host name of the web container.
651: * @throws JWebContainerServiceException when default host cannot be
652: * resolved (multiple services).
653: */
654: public String getDefaultHost() throws JWebContainerServiceException {
655: Map hosts = jettyServer.getHostMap();
656: // If we have more than 1 host, we cannot determine default host!
657: if (hosts.isEmpty()) {
658: String err = "Cannot determine default host : Jetty server has no host!";
659: throw new JWebContainerServiceException(err);
660: }
661:
662: String vHost = (String) hosts.keySet().iterator().next();
663: if (vHost == null) {
664: vHost = "localhost";
665: }
666:
667: if (hosts.size() > 1) {
668: if (getLogger().isLoggable(BasicLevel.WARN)) {
669: getLogger().log(
670: BasicLevel.WARN,
671: "More than 1 host found, using the first one : "
672: + vHost);
673: }
674: }
675: return vHost;
676: }
677:
678: /**
679: * Return the Default HTTP port number of the web container (can be null if
680: * multiple HTTP connector has been set).
681: * @return the Default HTTP port number of the web container.
682: * @throws JWebContainerServiceException when default HTTP port cannot be
683: * resolved (multiple occurences).
684: */
685: public String getDefaultHttpPort()
686: throws JWebContainerServiceException {
687: return String.valueOf(getFirstListenerFromScheme("http")
688: .getPort());
689: }
690:
691: /**
692: * Return the Default HTTPS port number of the web container (can be null if
693: * multiple HTTPS connector has been set).
694: * @return the Default HTTPS port number of the web container.
695: * @throws JWebContainerServiceException when default HTTPS port cannot be
696: * resolved (multiple occurences).
697: */
698: public String getDefaultHttpsPort()
699: throws JWebContainerServiceException {
700: return String.valueOf(getFirstListenerFromScheme("https")
701: .getPort());
702: }
703:
704: /**
705: * @param myScheme matching URL scheme (http, https, ...)
706: * @return Returns the first HttpListener found
707: */
708: private HttpListener getFirstListenerFromScheme(String myScheme) {
709: HttpListener[] listeners = jettyServer.getListeners();
710: Vector http = new Vector();
711: for (int i = 0; i < listeners.length; i++) {
712: String scheme = listeners[i].getDefaultScheme();
713: if (scheme.equalsIgnoreCase(myScheme)) {
714: http.add(listeners[i]);
715: }
716: }
717: if (http.isEmpty()) {
718: String err = "Cannot determine default '" + myScheme
719: + "' port :" + " Jetty server has 0 '" + myScheme
720: + "' Listener";
721: throw new JWebContainerServiceException(err);
722: }
723:
724: HttpListener hl = (HttpListener) http.get(0);
725: // Check if there are more than one HTTP connectors specified, if so, warn the administrator.
726: if (http.size() > 1) {
727: if (getLogger().isLoggable(BasicLevel.WARN)) {
728: getLogger().log(
729: BasicLevel.WARN,
730: "Found multiple Listener for scheme '"
731: + myScheme + "'"
732: + ", using first by default! (port:"
733: + hl.getPort() + ")");
734: }
735: }
736:
737: return hl;
738: }
739:
740: }
|