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 package org.apache.catalina.core;
18
19 import java.beans.PropertyChangeEvent;
20 import java.beans.PropertyChangeListener;
21 import java.io.File;
22 import java.util.Locale;
23 import java.util.concurrent.atomic.AtomicReference;
24
25 import org.apache.catalina.AccessLog;
26 import org.apache.catalina.Container;
27 import org.apache.catalina.ContainerEvent;
28 import org.apache.catalina.ContainerListener;
29 import org.apache.catalina.Context;
30 import org.apache.catalina.Engine;
31 import org.apache.catalina.Host;
32 import org.apache.catalina.Lifecycle;
33 import org.apache.catalina.LifecycleEvent;
34 import org.apache.catalina.LifecycleException;
35 import org.apache.catalina.LifecycleListener;
36 import org.apache.catalina.Realm;
37 import org.apache.catalina.Server;
38 import org.apache.catalina.Service;
39 import org.apache.catalina.connector.Request;
40 import org.apache.catalina.connector.Response;
41 import org.apache.catalina.realm.NullRealm;
42 import org.apache.catalina.util.ServerInfo;
43 import org.apache.juli.logging.Log;
44 import org.apache.juli.logging.LogFactory;
45
46 /**
47  * Standard implementation of the <b>Engine</b> interface.  Each
48  * child container must be a Host implementation to process the specific
49  * fully qualified host name of that virtual host. <br>
50  * You can set the jvmRoute direct or with the System.property <b>jvmRoute</b>.
51  *
52  * @author Craig R. McClanahan
53  */

54 public class StandardEngine extends ContainerBase implements Engine {
55
56     private static final Log log = LogFactory.getLog(StandardEngine.class);
57
58     // ----------------------------------------------------------- Constructors
59
60
61     /**
62      * Create a new StandardEngine component with the default basic Valve.
63      */

64     public StandardEngine() {
65
66         super();
67         pipeline.setBasic(new StandardEngineValve());
68         /* Set the jmvRoute using the system property jvmRoute */
69         try {
70             setJvmRoute(System.getProperty("jvmRoute"));
71         } catch(Exception ex) {
72             log.warn(sm.getString("standardEngine.jvmRouteFail"));
73         }
74         // By default, the engine will hold the reloading thread
75         backgroundProcessorDelay = 10;
76
77     }
78
79
80     // ----------------------------------------------------- Instance Variables
81
82
83     /**
84      * Host name to use when no server host, or an unknown host,
85      * is specified in the request.
86      */

87     private String defaultHost = null;
88
89
90     /**
91      * The <code>Service</code> that owns this Engine, if any.
92      */

93     private Service service = null;
94
95     /**
96      * The JVM Route ID for this Tomcat instance. All Route ID's must be unique
97      * across the cluster.
98      */

99     private String jvmRouteId;
100
101     /**
102      * Default access log to use for request/response pairs where we can't ID
103      * the intended host and context.
104      */

105     private final AtomicReference<AccessLog> defaultAccessLog =
106         new AtomicReference<>();
107
108     // ------------------------------------------------------------- Properties
109
110     /**
111      * Obtain the configured Realm and provide a default Realm implementation
112      * when no explicit configuration is set.
113      *
114      * @return configured realm, or a {@link NullRealm} by default
115      */

116     @Override
117     public Realm getRealm() {
118         Realm configured = super.getRealm();
119         // If no set realm has been called - default to NullRealm
120         // This can be overridden at engine, context and host level
121         if (configured == null) {
122             configured = new NullRealm();
123             this.setRealm(configured);
124         }
125         return configured;
126     }
127
128
129     /**
130      * Return the default host.
131      */

132     @Override
133     public String getDefaultHost() {
134         return defaultHost;
135     }
136
137
138     /**
139      * Set the default host.
140      *
141      * @param host The new default host
142      */

143     @Override
144     public void setDefaultHost(String host) {
145
146         String oldDefaultHost = this.defaultHost;
147         if (host == null) {
148             this.defaultHost = null;
149         } else {
150             this.defaultHost = host.toLowerCase(Locale.ENGLISH);
151         }
152         if (getState().isAvailable()) {
153             service.getMapper().setDefaultHostName(host);
154         }
155         support.firePropertyChange("defaultHost", oldDefaultHost,
156                                    this.defaultHost);
157
158     }
159
160
161     /**
162      * Set the cluster-wide unique identifier for this Engine.
163      * This value is only useful in a load-balancing scenario.
164      * <p>
165      * This property should not be changed once it is set.
166      */

167     @Override
168     public void setJvmRoute(String routeId) {
169         jvmRouteId = routeId;
170     }
171
172
173     /**
174      * Retrieve the cluster-wide unique identifier for this Engine.
175      * This value is only useful in a load-balancing scenario.
176      */

177     @Override
178     public String getJvmRoute() {
179         return jvmRouteId;
180     }
181
182
183     /**
184      * Return the <code>Service</code> with which we are associated (if any).
185      */

186     @Override
187     public Service getService() {
188         return this.service;
189     }
190
191
192     /**
193      * Set the <code>Service</code> with which we are associated (if any).
194      *
195      * @param service The service that owns this Engine
196      */

197     @Override
198     public void setService(Service service) {
199         this.service = service;
200     }
201
202     // --------------------------------------------------------- Public Methods
203
204
205     /**
206      * Add a child Container, only if the proposed child is an implementation
207      * of Host.
208      *
209      * @param child Child container to be added
210      */

211     @Override
212     public void addChild(Container child) {
213
214         if (!(child instanceof Host))
215             throw new IllegalArgumentException
216                 (sm.getString("standardEngine.notHost"));
217         super.addChild(child);
218
219     }
220
221
222     /**
223      * Disallow any attempt to set a parent for this Container, since an
224      * Engine is supposed to be at the top of the Container hierarchy.
225      *
226      * @param container Proposed parent Container
227      */

228     @Override
229     public void setParent(Container container) {
230
231         throw new IllegalArgumentException
232             (sm.getString("standardEngine.notParent"));
233
234     }
235
236
237     @Override
238     protected void initInternal() throws LifecycleException {
239         // Ensure that a Realm is present before any attempt is made to start
240         // one. This will create the default NullRealm if necessary.
241         getRealm();
242         super.initInternal();
243     }
244
245
246     /**
247      * Start this component and implement the requirements
248      * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
249      *
250      * @exception LifecycleException if this component detects a fatal error
251      *  that prevents this component from being used
252      */

253     @Override
254     protected synchronized void startInternal() throws LifecycleException {
255
256         // Log our server identification information
257         if (log.isInfoEnabled()) {
258             log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
259         }
260
261         // Standard container startup
262         super.startInternal();
263     }
264
265
266     /**
267      * Override the default implementation. If no access log is defined for the
268      * Engine, look for one in the Engine's default host and then the default
269      * host's ROOT context. If still none is found, return the default NoOp
270      * access log.
271      */

272     @Override
273     public void logAccess(Request request, Response response, long time,
274             boolean useDefault) {
275
276         boolean logged = false;
277
278         if (getAccessLog() != null) {
279             accessLog.log(request, response, time);
280             logged = true;
281         }
282
283         if (!logged && useDefault) {
284             AccessLog newDefaultAccessLog = defaultAccessLog.get();
285             if (newDefaultAccessLog == null) {
286                 // If we reached this point, this Engine can't have an AccessLog
287                 // Look in the defaultHost
288                 Host host = (Host) findChild(getDefaultHost());
289                 Context context = null;
290                 if (host != null && host.getState().isAvailable()) {
291                     newDefaultAccessLog = host.getAccessLog();
292
293                     if (newDefaultAccessLog != null) {
294                         if (defaultAccessLog.compareAndSet(null,
295                                 newDefaultAccessLog)) {
296                             AccessLogListener l = new AccessLogListener(this,
297                                     host, null);
298                             l.install();
299                         }
300                     } else {
301                         // Try the ROOT context of default host
302                         context = (Context) host.findChild("");
303                         if (context != null &&
304                                 context.getState().isAvailable()) {
305                             newDefaultAccessLog = context.getAccessLog();
306                             if (newDefaultAccessLog != null) {
307                                 if (defaultAccessLog.compareAndSet(null,
308                                         newDefaultAccessLog)) {
309                                     AccessLogListener l = new AccessLogListener(
310                                             thisnull, context);
311                                     l.install();
312                                 }
313                             }
314                         }
315                     }
316                 }
317
318                 if (newDefaultAccessLog == null) {
319                     newDefaultAccessLog = new NoopAccessLog();
320                     if (defaultAccessLog.compareAndSet(null,
321                             newDefaultAccessLog)) {
322                         AccessLogListener l = new AccessLogListener(this, host,
323                                 context);
324                         l.install();
325                     }
326                 }
327             }
328
329             newDefaultAccessLog.log(request, response, time);
330         }
331     }
332
333
334     /**
335      * Return the parent class loader for this component.
336      */

337     @Override
338     public ClassLoader getParentClassLoader() {
339         if (parentClassLoader != null)
340             return parentClassLoader;
341         if (service != null) {
342             return service.getParentClassLoader();
343         }
344         return ClassLoader.getSystemClassLoader();
345     }
346
347
348     @Override
349     public File getCatalinaBase() {
350         if (service != null) {
351             Server s = service.getServer();
352             if (s != null) {
353                 File base = s.getCatalinaBase();
354                 if (base != null) {
355                     return base;
356                 }
357             }
358         }
359         // Fall-back
360         return super.getCatalinaBase();
361     }
362
363
364     @Override
365     public File getCatalinaHome() {
366         if (service != null) {
367             Server s = service.getServer();
368             if (s != null) {
369                 File base = s.getCatalinaHome();
370                 if (base != null) {
371                     return base;
372                 }
373             }
374         }
375         // Fall-back
376         return super.getCatalinaHome();
377     }
378
379
380     // -------------------- JMX registration  --------------------
381
382     @Override
383     protected String getObjectNameKeyProperties() {
384         return "type=Engine";
385     }
386
387
388     @Override
389     protected String getDomainInternal() {
390         return getName();
391     }
392
393
394     // ----------------------------------------------------------- Inner classes
395     protected static final class NoopAccessLog implements AccessLog {
396
397         @Override
398         public void log(Request request, Response response, long time) {
399             // NOOP
400         }
401
402         @Override
403         public void setRequestAttributesEnabled(
404                 boolean requestAttributesEnabled) {
405             // NOOP
406
407         }
408
409         @Override
410         public boolean getRequestAttributesEnabled() {
411             // NOOP
412             return false;
413         }
414     }
415
416     protected static final class AccessLogListener
417             implements PropertyChangeListener, LifecycleListener,
418             ContainerListener {
419
420         private final StandardEngine engine;
421         private final Host host;
422         private final Context context;
423         private volatile boolean disabled = false;
424
425         public AccessLogListener(StandardEngine engine, Host host,
426                 Context context) {
427             this.engine = engine;
428             this.host = host;
429             this.context = context;
430         }
431
432         public void install() {
433             engine.addPropertyChangeListener(this);
434             if (host != null) {
435                 host.addContainerListener(this);
436                 host.addLifecycleListener(this);
437             }
438             if (context != null) {
439                 context.addLifecycleListener(this);
440             }
441         }
442
443         private void uninstall() {
444             disabled = true;
445             if (context != null) {
446                 context.removeLifecycleListener(this);
447             }
448             if (host != null) {
449                 host.removeLifecycleListener(this);
450                 host.removeContainerListener(this);
451             }
452             engine.removePropertyChangeListener(this);
453         }
454
455         @Override
456         public void lifecycleEvent(LifecycleEvent event) {
457             if (disabled) return;
458
459             String type = event.getType();
460             if (Lifecycle.AFTER_START_EVENT.equals(type) ||
461                     Lifecycle.BEFORE_STOP_EVENT.equals(type) ||
462                     Lifecycle.BEFORE_DESTROY_EVENT.equals(type)) {
463                 // Container is being started/stopped/removed
464                 // Force re-calculation and disable listener since it won't
465                 // be re-used
466                 engine.defaultAccessLog.set(null);
467                 uninstall();
468             }
469         }
470
471         @Override
472         public void propertyChange(PropertyChangeEvent evt) {
473             if (disabled) return;
474             if ("defaultHost".equals(evt.getPropertyName())) {
475                 // Force re-calculation and disable listener since it won't
476                 // be re-used
477                 engine.defaultAccessLog.set(null);
478                 uninstall();
479             }
480         }
481
482         @Override
483         public void containerEvent(ContainerEvent event) {
484             // Only useful for hosts
485             if (disabled) return;
486             if (Container.ADD_CHILD_EVENT.equals(event.getType())) {
487                 Context context = (Context) event.getData();
488                 if ("".equals(context.getPath())) {
489                     // Force re-calculation and disable listener since it won't
490                     // be re-used
491                     engine.defaultAccessLog.set(null);
492                     uninstall();
493                 }
494             }
495         }
496     }
497 }
498