1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.jk.server;
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.PrintStream;
25 import java.util.Enumeration;
26 import java.util.Hashtable;
27 import java.util.Properties;
28 import java.util.StringTokenizer;
29 import java.util.Vector;
30
31 import javax.management.MBeanRegistration;
32 import javax.management.MBeanServer;
33 import javax.management.ObjectName;
34
35 import org.apache.jk.core.JkHandler;
36 import org.apache.jk.core.WorkerEnv;
37 import org.apache.tomcat.util.IntrospectionUtils;
38 import org.apache.tomcat.util.modeler.Registry;
39
40 /** Main class used to startup and configure jk. It manages the conf/jk2.properties file
41 * and is the target of JMX proxy.
42 *
43 * It implements a policy of save-on-change - whenever a property is changed at
44 * runtime the jk2.properties file will be overriden.
45 *
46 * You can edit the config file when tomcat is stoped ( or if you don't use JMX or
47 * other admin tools ).
48 *
49 * The format of jk2.properties:
50 * <dl>
51 * <dt>TYPE[.LOCALNAME].PROPERTY_NAME=VALUE
52 * <dd>Set a property on the associated component. TYPE will be used to
53 * find the class name and instantiate the component. LOCALNAME allows
54 * multiple instances. In JMX mode, TYPE and LOCALNAME will form the
55 * JMX name ( eventually combined with a 'jk2' component )
56 *
57 * <dt>NAME=VALUE
58 * <dd>Define global properties to be used in ${} substitutions
59 *
60 * <dt>class.COMPONENT_TYPE=JAVA_CLASS_NAME
61 * <dd>Adds a new 'type' of component. We predefine all known types.
62 * </dl>
63 *
64 * Instances are created the first time a component name is found. In addition,
65 * 'handler.list' property will override the list of 'default' components that are
66 * loaded automatically.
67 *
68 * Note that the properties file is just one (simplistic) way to configure jk. We hope
69 * to see configs based on registry, LDAP, db, etc. ( XML is not necesarily better )
70 *
71 * @author Costin Manolache
72 */
73 public class JkMain implements MBeanRegistration
74 {
75 WorkerEnv wEnv;
76 String propFile;
77 Properties props=new Properties();
78
79 Properties modules=new Properties();
80 boolean modified=false;
81 boolean started=false;
82 boolean saveProperties=false;
83
84 public JkMain()
85 {
86 JkMain.jkMain=this;
87 modules.put("channelSocket", "org.apache.jk.common.ChannelSocket");
88 modules.put("channelNioSocket", "org.apache.jk.common.ChannelNioSocket");
89 modules.put("channelUnix", "org.apache.jk.common.ChannelUn");
90 modules.put("channelJni", "org.apache.jk.common.ChannelJni");
91 modules.put("apr", "org.apache.jk.apr.AprImpl");
92 modules.put("mx", "org.apache.jk.common.JkMX");
93 modules.put("modeler", "org.apache.jk.common.JkModeler");
94 modules.put("shm", "org.apache.jk.common.Shm");
95 modules.put("request","org.apache.jk.common.HandlerRequest");
96 modules.put("container","org.apache.jk.common.HandlerRequest");
97 modules.put("modjk","org.apache.jk.common.ModJkMX");
98
99 }
100
101 public static JkMain getJkMain() {
102 return jkMain;
103 }
104
105 private static String DEFAULT_HTTPS="com.sun.net.ssl.internal.www.protocol";
106 private void initHTTPSUrls() {
107 try {
108 // 11657: if only ajp is used, https: redirects need to work ( at least for 1.3+)
109 String value = System.getProperty("java.protocol.handler.pkgs");
110 if (value == null) {
111 value = DEFAULT_HTTPS;
112 } else if (value.indexOf(DEFAULT_HTTPS) >= 0 ) {
113 return; // already set
114 } else {
115 value += "|" + DEFAULT_HTTPS;
116 }
117 System.setProperty("java.protocol.handler.pkgs", value);
118 } catch(Exception ex ) {
119 log.info("Error adding SSL Protocol Handler",ex);
120 }
121 }
122
123 // -------------------- Setting --------------------
124
125 /** Load a .properties file into and set the values
126 * into jk2 configuration.
127 */
128 public void setPropertiesFile( String p ) {
129 propFile=p;
130 if( started ) {
131 loadPropertiesFile();
132 }
133 }
134
135 public String getPropertiesFile() {
136 return propFile;
137 }
138
139 public void setSaveProperties( boolean b ) {
140 saveProperties=b;
141 }
142
143 /** Set a name/value as a jk2 property
144 */
145 public void setProperty( String n, String v ) {
146 if( "jkHome".equals( n ) ) {
147 setJkHome( v );
148 }
149 if( "propertiesFile".equals( n ) ) {
150 setPropertiesFile( v );
151 }
152 props.put( n, v );
153 if( started ) {
154 processProperty( n, v );
155 saveProperties();
156 }
157 }
158 /**
159 * Retrieve a property.
160 */
161 public Object getProperty(String name) {
162 String alias = (String)replacements.get(name);
163 Object result = null;
164 if(alias != null) {
165 result = props.get(alias);
166 }
167 if(result == null) {
168 result = props.get(name);
169 }
170 return result;
171 }
172 /**
173 * Set the <code>channelClassName</code> that will used to connect to
174 * httpd.
175 */
176 public void setChannelClassName(String name) {
177 props.put( "handler.channel.className",name);
178 }
179
180 public String getChannelClassName() {
181 return (String)props.get( "handler.channel.className");
182 }
183
184 /**
185 * Set the <code>workerClassName</code> that will handle the request.
186 * ( sort of 'pivot' in axis :-)
187 */
188 public void setWorkerClassName(String name) {
189 props.put( "handler.container.className",name);
190 }
191
192 public String getWorkerClassName() {
193 return (String)props.get( "handler.container.className");
194 }
195
196 /** Set the base dir of jk2. ( including WEB-INF if in a webapp ).
197 * We'll try to guess it from classpath if none is set ( for
198 * example on command line ), but if in a servlet environment
199 * you need to use Context.getRealPath or a system property or
200 * set it expliciltey.
201 */
202 public void setJkHome( String s ) {
203 getWorkerEnv().setJkHome(s);
204 }
205
206 public String getJkHome() {
207 return getWorkerEnv().getJkHome();
208 }
209
210 String out;
211 String err;
212 File propsF;
213
214 public void setOut( String s ) {
215 this.out=s;
216 }
217
218 public String getOut() {
219 return this.out;
220 }
221
222 public void setErr( String s ) {
223 this.err=s;
224 }
225
226 public String getErr() {
227 return this.err;
228 }
229
230 // -------------------- Initialization --------------------
231
232 public void init() throws IOException
233 {
234 long t1=System.currentTimeMillis();
235 if(null != out) {
236 PrintStream outS=new PrintStream(new FileOutputStream(out));
237 System.setOut(outS);
238 }
239 if(null != err) {
240 PrintStream errS=new PrintStream(new FileOutputStream(err));
241 System.setErr(errS);
242 }
243
244 String home=getWorkerEnv().getJkHome();
245 if( home==null ) {
246 // XXX use IntrospectionUtil to find myself
247 this.guessHome();
248 }
249 home=getWorkerEnv().getJkHome();
250 if( home==null ) {
251 log.info( "Can't find home, jk2.properties not loaded");
252 }
253 if(log.isDebugEnabled())
254 log.debug("Starting Jk2, base dir= " + home );
255 loadPropertiesFile();
256
257 String initHTTPS = (String)props.get("class.initHTTPS");
258 if("true".equalsIgnoreCase(initHTTPS)) {
259 initHTTPSUrls();
260 }
261
262 long t2=System.currentTimeMillis();
263 initTime=t2-t1;
264 }
265
266 static String defaultHandlers[]= { "request",
267 "container",
268 "channelSocket"};
269 /*
270 static String defaultHandlers[]= { "apr",
271 "shm",
272 "request",
273 "container",
274 "channelSocket",
275 "channelJni",
276 "channelUnix"};
277 */
278
279 public void stop()
280 {
281 for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
282 if( wEnv.getHandler(i) != null ) {
283 try {
284 wEnv.getHandler(i).destroy();
285 } catch( IOException ex) {
286 log.error("Error stopping " + wEnv.getHandler(i).getName(), ex);
287 }
288 }
289 }
290
291 started=false;
292 }
293
294 public void start() throws IOException
295 {
296 long t1=System.currentTimeMillis();
297 // We must have at least 3 handlers:
298 // channel is the 'transport'
299 // request is the request processor or 'global' chain
300 // container is the 'provider'
301 // Additional handlers may exist and be used internally
302 // or be chained to create one of the standard handlers
303
304 String handlers[]=defaultHandlers;
305 // backward compat
306 String workers=props.getProperty( "handler.list", null );
307 if( workers!=null ) {
308 handlers= split( workers, ",");
309 }
310
311 // Load additional component declarations
312 processModules();
313
314 for( int i=0; i<handlers.length; i++ ) {
315 String name= handlers[i];
316 JkHandler w=getWorkerEnv().getHandler( name );
317 if( w==null ) {
318 newHandler( name, "", name );
319 }
320 }
321
322 // Process properties - and add aditional handlers.
323 processProperties();
324
325 for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
326 if( wEnv.getHandler(i) != null ) {
327 try {
328 wEnv.getHandler(i).init();
329 } catch( IOException ex) {
330 if( "apr".equals(wEnv.getHandler(i).getName() )) {
331 log.info( "APR not loaded, disabling jni components: " + ex.toString());
332 } else {
333 log.error( "error initializing " + wEnv.getHandler(i).getName(), ex );
334 }
335 }
336 }
337 }
338
339 started=true;
340 long t2=System.currentTimeMillis();
341 startTime=t2-t1;
342
343 this.saveProperties();
344 log.info("Jk running ID=" + wEnv.getLocalId() + " time=" + initTime + "/" + startTime +
345 " config=" + propFile);
346 }
347
348 // -------------------- Usefull methods --------------------
349
350 public WorkerEnv getWorkerEnv() {
351 if( wEnv==null ) {
352 wEnv=new WorkerEnv();
353 }
354 return wEnv;
355 }
356
357 public void setWorkerEnv(WorkerEnv wEnv) {
358 this.wEnv = wEnv;
359 }
360
361 /* A bit of magic to support workers.properties without giving
362 up the clean get/set
363 */
364 public void setBeanProperty( Object target, String name, String val ) {
365 if( val!=null )
366 val=IntrospectionUtils.replaceProperties( val, props, null );
367 if( log.isDebugEnabled())
368 log.debug( "setProperty " + target + " " + name + "=" + val );
369
370 IntrospectionUtils.setProperty( target, name, val );
371 }
372
373 /*
374 * Set a handler property
375 */
376 public void setPropertyString( String handlerN, String name, String val ) {
377 if( log.isDebugEnabled() )
378 log.debug( "setProperty " + handlerN + " " + name + "=" + val );
379 Object target=getWorkerEnv().getHandler( handlerN );
380
381 setBeanProperty( target, name, val );
382 if( started ) {
383 saveProperties();
384 }
385
386 }
387
388 /** The time it took to initialize jk ( ms)
389 */
390 public long getInitTime() {
391 return initTime;
392 }
393
394 /** The time it took to start jk ( ms )
395 */
396 public long getStartTime() {
397 return startTime;
398 }
399
400 // -------------------- Main --------------------
401
402 long initTime;
403 long startTime;
404 static JkMain jkMain=null;
405
406 public static void main(String args[]) {
407 try {
408 if( args.length == 1 &&
409 ( "-?".equals(args[0]) || "-h".equals( args[0])) ) {
410 System.out.println("Usage: ");
411 System.out.println(" JkMain [args]");
412 System.out.println();
413 System.out.println(" Each bean setter corresponds to an arg ( like -debug 10 )");
414 System.out.println(" System properties:");
415 System.out.println(" jk2.home Base dir of jk2");
416 return;
417 }
418
419 jkMain=new JkMain();
420
421 IntrospectionUtils.processArgs( jkMain, args, new String[] {},
422 null, new Hashtable());
423
424 jkMain.init();
425 jkMain.start();
426 } catch( Exception ex ) {
427 log.warn("Error running",ex);
428 }
429 }
430
431 // -------------------- Private methods --------------------
432
433
434 private boolean checkPropertiesFile() {
435 if(propFile == null) {
436 return false;
437 }
438 propsF = new File(propFile);
439 if(!propsF.isAbsolute()) {
440 String home = getWorkerEnv().getJkHome();
441 if( home == null ) {
442 return false;
443 }
444 propsF = new File(home, propFile);
445 }
446 return propsF.exists();
447 }
448
449 private void loadPropertiesFile() {
450 if(!checkPropertiesFile()) {
451 return;
452 }
453
454 try {
455 props.load( new FileInputStream(propsF) );
456 } catch(IOException ex ){
457 log.warn("Unable to load properties from "+propsF,ex);
458 }
459 }
460
461 public void saveProperties() {
462 if( !saveProperties) return;
463
464 if(propsF == null) {
465 log.warn("No properties file specified. Unable to save");
466 return;
467 }
468 // Temp - to check if it works
469 File outFile= new File(propsF.getParentFile(), propsF.getName()+".save");
470 log.debug("Saving properties " + outFile );
471 try {
472 props.store( new FileOutputStream(outFile), "AUTOMATICALLY GENERATED" );
473 } catch(IOException ex ){
474 log.warn("Unable to save to "+outFile,ex);
475 }
476 }
477
478 // translate top-level keys ( from coyote or generic ) into component keys
479 static Hashtable replacements=new Hashtable();
480 static {
481 replacements.put("port","channelSocket.port");
482 replacements.put("maxThreads", "channelSocket.maxThreads");
483 replacements.put("minSpareThreads", "channelSocket.minSpareThreads");
484 replacements.put("maxSpareThreads", "channelSocket.maxSpareThreads");
485 replacements.put("backlog", "channelSocket.backlog");
486 replacements.put("tcpNoDelay", "channelSocket.tcpNoDelay");
487 replacements.put("soTimeout", "channelSocket.soTimeout");
488 replacements.put("timeout", "channelSocket.timeout");
489 replacements.put("address", "channelSocket.address");
490 replacements.put("bufferSize", "channelSocket.bufferSize");
491 replacements.put("tomcatAuthentication", "request.tomcatAuthentication");
492 replacements.put("packetSize", "channelSocket.packetSize");
493 }
494
495 private void preProcessProperties() {
496 Enumeration keys=props.keys();
497 Vector v=new Vector();
498
499 while( keys.hasMoreElements() ) {
500 String key=(String)keys.nextElement();
501 Object newName=replacements.get(key);
502 if( newName !=null ) {
503 v.addElement(key);
504 }
505 }
506 keys=v.elements();
507 while( keys.hasMoreElements() ) {
508 String key=(String)keys.nextElement();
509 Object propValue=props.getProperty( key );
510 String replacement=(String)replacements.get(key);
511 props.put(replacement, propValue);
512 if( log.isDebugEnabled())
513 log.debug("Substituting " + key + " " + replacement + " " +
514 propValue);
515 }
516 }
517
518 private void processProperties() {
519 preProcessProperties();
520 Enumeration keys=props.keys();
521
522 while( keys.hasMoreElements() ) {
523 String name=(String)keys.nextElement();
524 String propValue=props.getProperty( name );
525
526 processProperty( name, propValue );
527 }
528 }
529
530 private void processProperty(String name, String propValue) {
531 String type=name;
532 String fullName=name;
533 String localName="";
534 String propName="";
535 // ignore
536 if( name.startsWith("key.")) return;
537
538 int dot=name.indexOf(".");
539 int lastDot=name.lastIndexOf(".");
540 if( dot > 0 ) {
541 type=name.substring(0, dot );
542 if( dot != lastDot ) {
543 localName=name.substring( dot + 1, lastDot );
544 fullName=type + "." + localName;
545 } else {
546 fullName=type;
547 }
548 propName=name.substring( lastDot+1);
549 } else {
550 return;
551 }
552
553 if( log.isDebugEnabled() )
554 log.debug( "Processing " + type + ":" + localName + ":" + fullName + " " + propName );
555 if( "class".equals( type ) || "handler".equals( type ) ) {
556 return;
557 }
558
559 JkHandler comp=getWorkerEnv().getHandler( fullName );
560 if( comp==null ) {
561 comp=newHandler( type, localName, fullName );
562 }
563 if( comp==null )
564 return;
565
566 if( log.isDebugEnabled() )
567 log.debug("Setting " + propName + " on " + fullName + " " + comp);
568 this.setBeanProperty( comp, propName, propValue );
569 }
570
571 private JkHandler newHandler( String type, String localName, String fullName )
572 {
573 JkHandler handler;
574 String classN=modules.getProperty(type);
575 if( classN == null ) {
576 log.error("No class name for " + fullName + " " + type );
577 return null;
578 }
579 try {
580 Class channelclass = Class.forName(classN);
581 handler=(JkHandler)channelclass.newInstance();
582 } catch (Throwable ex) {
583 handler=null;
584 log.error( "Can't create " + fullName, ex );
585 return null;
586 }
587 if( this.domain != null ) {
588 try {
589 ObjectName handlerOname = new ObjectName
590 (this.domain + ":" + "type=JkHandler,name=" + fullName);
591 Registry.getRegistry(null, null).registerComponent(handler, handlerOname, classN);
592 } catch (Exception e) {
593 log.error( "Error registering " + fullName, e );
594 }
595
596 }
597 wEnv.addHandler( fullName, handler );
598 return handler;
599 }
600
601 private void processModules() {
602 Enumeration keys=props.keys();
603 int plen=6;
604
605 while( keys.hasMoreElements() ) {
606 String k=(String)keys.nextElement();
607 if( ! k.startsWith( "class." ) )
608 continue;
609
610 String name= k.substring( plen );
611 String propValue=props.getProperty( k );
612
613 if( log.isDebugEnabled()) log.debug("Register " + name + " " + propValue );
614 modules.put( name, propValue );
615 }
616 }
617
618 private String[] split(String s, String delim ) {
619 Vector v=new Vector();
620 StringTokenizer st=new StringTokenizer(s, delim );
621 while( st.hasMoreTokens() ) {
622 v.addElement( st.nextToken());
623 }
624 String res[]=new String[ v.size() ];
625 for( int i=0; i<res.length; i++ ) {
626 res[i]=(String)v.elementAt(i);
627 }
628 return res;
629 }
630
631 // guessing home
632 private static String CNAME="org/apache/jk/server/JkMain.class";
633
634 private void guessHome() {
635 String home= wEnv.getJkHome();
636 if( home != null )
637 return;
638 home=IntrospectionUtils.guessInstall( "jk2.home","jk2.home",
639 "tomcat-jk2.jar", CNAME );
640 if( home != null ) {
641 log.info("Guessed home " + home );
642 wEnv.setJkHome( home );
643 }
644 }
645
646 static org.apache.juli.logging.Log log=
647 org.apache.juli.logging.LogFactory.getLog( JkMain.class );
648
649 protected String domain;
650 protected ObjectName oname;
651 protected MBeanServer mserver;
652
653 public ObjectName getObjectName() {
654 return oname;
655 }
656
657 public String getDomain() {
658 return domain;
659 }
660
661 public ObjectName preRegister(MBeanServer server,
662 ObjectName name) throws Exception {
663 oname=name;
664 mserver=server;
665 domain=name.getDomain();
666 return name;
667 }
668
669 public void postRegister(Boolean registrationDone) {
670 }
671
672 public void preDeregister() throws Exception {
673 }
674
675 public void postDeregister() {
676 }
677
678 public void pause() throws Exception {
679 for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
680 if( wEnv.getHandler(i) != null ) {
681 wEnv.getHandler(i).pause();
682 }
683 }
684 }
685
686 public void resume() throws Exception {
687 for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
688 if( wEnv.getHandler(i) != null ) {
689 wEnv.getHandler(i).resume();
690 }
691 }
692 }
693
694
695 }