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 this, null, 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