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
20 import java.beans.PropertyChangeListener;
21 import java.beans.PropertyChangeSupport;
22 import java.util.ArrayList;
23
24 import javax.management.ObjectName;
25
26 import org.apache.catalina.Container;
27 import org.apache.catalina.Engine;
28 import org.apache.catalina.Executor;
29 import org.apache.catalina.JmxEnabled;
30 import org.apache.catalina.LifecycleException;
31 import org.apache.catalina.LifecycleState;
32 import org.apache.catalina.Server;
33 import org.apache.catalina.Service;
34 import org.apache.catalina.connector.Connector;
35 import org.apache.catalina.mapper.Mapper;
36 import org.apache.catalina.mapper.MapperListener;
37 import org.apache.catalina.util.LifecycleMBeanBase;
38 import org.apache.juli.logging.Log;
39 import org.apache.juli.logging.LogFactory;
40 import org.apache.tomcat.util.res.StringManager;
41
42
43 /**
44 * Standard implementation of the <code>Service</code> interface. The
45 * associated Container is generally an instance of Engine, but this is
46 * not required.
47 *
48 * @author Craig R. McClanahan
49 */
50
51 public class StandardService extends LifecycleMBeanBase implements Service {
52
53 private static final Log log = LogFactory.getLog(StandardService.class);
54
55
56 // ----------------------------------------------------- Instance Variables
57
58 /**
59 * The name of this service.
60 */
61 private String name = null;
62
63
64 /**
65 * The string manager for this package.
66 */
67 private static final StringManager sm =
68 StringManager.getManager(Constants.Package);
69
70 /**
71 * The <code>Server</code> that owns this Service, if any.
72 */
73 private Server server = null;
74
75 /**
76 * The property change support for this component.
77 */
78 protected final PropertyChangeSupport support = new PropertyChangeSupport(this);
79
80
81 /**
82 * The set of Connectors associated with this Service.
83 */
84 protected Connector connectors[] = new Connector[0];
85 private final Object connectorsLock = new Object();
86
87 /**
88 * The list of executors held by the service.
89 */
90 protected final ArrayList<Executor> executors = new ArrayList<>();
91
92 private Engine engine = null;
93
94 private ClassLoader parentClassLoader = null;
95
96 /**
97 * Mapper.
98 */
99 protected final Mapper mapper = new Mapper();
100
101
102 /**
103 * Mapper listener.
104 */
105 protected final MapperListener mapperListener = new MapperListener(this);
106
107
108 // ------------------------------------------------------------- Properties
109
110 @Override
111 public Mapper getMapper() {
112 return mapper;
113 }
114
115
116 @Override
117 public Engine getContainer() {
118 return engine;
119 }
120
121
122 @Override
123 public void setContainer(Engine engine) {
124 Engine oldEngine = this.engine;
125 if (oldEngine != null) {
126 oldEngine.setService(null);
127 }
128 this.engine = engine;
129 if (this.engine != null) {
130 this.engine.setService(this);
131 }
132 if (getState().isAvailable()) {
133 if (this.engine != null) {
134 try {
135 this.engine.start();
136 } catch (LifecycleException e) {
137 log.error(sm.getString("standardService.engine.startFailed"), e);
138 }
139 }
140 // Restart MapperListener to pick up new engine.
141 try {
142 mapperListener.stop();
143 } catch (LifecycleException e) {
144 log.error(sm.getString("standardService.mapperListener.stopFailed"), e);
145 }
146 try {
147 mapperListener.start();
148 } catch (LifecycleException e) {
149 log.error(sm.getString("standardService.mapperListener.startFailed"), e);
150 }
151 if (oldEngine != null) {
152 try {
153 oldEngine.stop();
154 } catch (LifecycleException e) {
155 log.error(sm.getString("standardService.engine.stopFailed"), e);
156 }
157 }
158 }
159
160 // Report this property change to interested listeners
161 support.firePropertyChange("container", oldEngine, this.engine);
162 }
163
164
165 /**
166 * Return the name of this Service.
167 */
168 @Override
169 public String getName() {
170 return name;
171 }
172
173
174 /**
175 * Set the name of this Service.
176 *
177 * @param name The new service name
178 */
179 @Override
180 public void setName(String name) {
181 this.name = name;
182 }
183
184
185 /**
186 * Return the <code>Server</code> with which we are associated (if any).
187 */
188 @Override
189 public Server getServer() {
190 return this.server;
191 }
192
193
194 /**
195 * Set the <code>Server</code> with which we are associated (if any).
196 *
197 * @param server The server that owns this Service
198 */
199 @Override
200 public void setServer(Server server) {
201 this.server = server;
202 }
203
204
205 // --------------------------------------------------------- Public Methods
206
207
208 /**
209 * Add a new Connector to the set of defined Connectors, and associate it
210 * with this Service's Container.
211 *
212 * @param connector The Connector to be added
213 */
214 @Override
215 public void addConnector(Connector connector) {
216
217 synchronized (connectorsLock) {
218 connector.setService(this);
219 Connector results[] = new Connector[connectors.length + 1];
220 System.arraycopy(connectors, 0, results, 0, connectors.length);
221 results[connectors.length] = connector;
222 connectors = results;
223 }
224
225 try {
226 if (getState().isAvailable()) {
227 connector.start();
228 }
229 } catch (LifecycleException e) {
230 throw new IllegalArgumentException(
231 sm.getString("standardService.connector.startFailed", connector), e);
232 }
233
234 // Report this property change to interested listeners
235 support.firePropertyChange("connector", null, connector);
236 }
237
238
239 public ObjectName[] getConnectorNames() {
240 ObjectName results[] = new ObjectName[connectors.length];
241 for (int i=0; i<results.length; i++) {
242 results[i] = connectors[i].getObjectName();
243 }
244 return results;
245 }
246
247
248 /**
249 * Add a property change listener to this component.
250 *
251 * @param listener The listener to add
252 */
253 public void addPropertyChangeListener(PropertyChangeListener listener) {
254 support.addPropertyChangeListener(listener);
255 }
256
257
258 /**
259 * Find and return the set of Connectors associated with this Service.
260 */
261 @Override
262 public Connector[] findConnectors() {
263 return connectors;
264 }
265
266
267 /**
268 * Remove the specified Connector from the set associated from this
269 * Service. The removed Connector will also be disassociated from our
270 * Container.
271 *
272 * @param connector The Connector to be removed
273 */
274 @Override
275 public void removeConnector(Connector connector) {
276
277 synchronized (connectorsLock) {
278 int j = -1;
279 for (int i = 0; i < connectors.length; i++) {
280 if (connector == connectors[i]) {
281 j = i;
282 break;
283 }
284 }
285 if (j < 0)
286 return;
287 if (connectors[j].getState().isAvailable()) {
288 try {
289 connectors[j].stop();
290 } catch (LifecycleException e) {
291 log.error(sm.getString(
292 "standardService.connector.stopFailed",
293 connectors[j]), e);
294 }
295 }
296 connector.setService(null);
297 int k = 0;
298 Connector results[] = new Connector[connectors.length - 1];
299 for (int i = 0; i < connectors.length; i++) {
300 if (i != j)
301 results[k++] = connectors[i];
302 }
303 connectors = results;
304
305 // Report this property change to interested listeners
306 support.firePropertyChange("connector", connector, null);
307 }
308 }
309
310
311 /**
312 * Remove a property change listener from this component.
313 *
314 * @param listener The listener to remove
315 */
316 public void removePropertyChangeListener(PropertyChangeListener listener) {
317 support.removePropertyChangeListener(listener);
318 }
319
320
321 /**
322 * Return a String representation of this component.
323 */
324 @Override
325 public String toString() {
326 StringBuilder sb = new StringBuilder("StandardService[");
327 sb.append(getName());
328 sb.append("]");
329 return sb.toString();
330 }
331
332
333 /**
334 * Adds a named executor to the service
335 * @param ex Executor
336 */
337 @Override
338 public void addExecutor(Executor ex) {
339 synchronized (executors) {
340 if (!executors.contains(ex)) {
341 executors.add(ex);
342 if (getState().isAvailable()) {
343 try {
344 ex.start();
345 } catch (LifecycleException x) {
346 log.error(sm.getString("standardService.executor.start"), x);
347 }
348 }
349 }
350 }
351 }
352
353
354 /**
355 * Retrieves all executors
356 * @return Executor[]
357 */
358 @Override
359 public Executor[] findExecutors() {
360 synchronized (executors) {
361 Executor[] arr = new Executor[executors.size()];
362 executors.toArray(arr);
363 return arr;
364 }
365 }
366
367
368 /**
369 * Retrieves executor by name, null if not found
370 * @param executorName String
371 * @return Executor
372 */
373 @Override
374 public Executor getExecutor(String executorName) {
375 synchronized (executors) {
376 for (Executor executor: executors) {
377 if (executorName.equals(executor.getName()))
378 return executor;
379 }
380 }
381 return null;
382 }
383
384
385 /**
386 * Removes an executor from the service
387 * @param ex Executor
388 */
389 @Override
390 public void removeExecutor(Executor ex) {
391 synchronized (executors) {
392 if ( executors.remove(ex) && getState().isAvailable() ) {
393 try {
394 ex.stop();
395 } catch (LifecycleException e) {
396 log.error(sm.getString("standardService.executor.stop"), e);
397 }
398 }
399 }
400 }
401
402
403 /**
404 * Start nested components ({@link Executor}s, {@link Connector}s and
405 * {@link Container}s) and implement the requirements of
406 * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
407 *
408 * @exception LifecycleException if this component detects a fatal error
409 * that prevents this component from being used
410 */
411 @Override
412 protected void startInternal() throws LifecycleException {
413
414 if(log.isInfoEnabled())
415 log.info(sm.getString("standardService.start.name", this.name));
416 setState(LifecycleState.STARTING);
417
418 // Start our defined Container first
419 if (engine != null) {
420 synchronized (engine) {
421 engine.start();
422 }
423 }
424
425 synchronized (executors) {
426 for (Executor executor: executors) {
427 executor.start();
428 }
429 }
430
431 mapperListener.start();
432
433 // Start our defined Connectors second
434 synchronized (connectorsLock) {
435 for (Connector connector: connectors) {
436 // If it has already failed, don't try and start it
437 if (connector.getState() != LifecycleState.FAILED) {
438 connector.start();
439 }
440 }
441 }
442 }
443
444
445 /**
446 * Stop nested components ({@link Executor}s, {@link Connector}s and
447 * {@link Container}s) and implement the requirements of
448 * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
449 *
450 * @exception LifecycleException if this component detects a fatal error
451 * that needs to be reported
452 */
453 @Override
454 protected void stopInternal() throws LifecycleException {
455
456 // Pause connectors first
457 synchronized (connectorsLock) {
458 for (Connector connector: connectors) {
459 connector.pause();
460 // Close server socket if bound on start
461 // Note: test is in AbstractEndpoint
462 connector.getProtocolHandler().closeServerSocketGraceful();
463 }
464 }
465
466 if(log.isInfoEnabled())
467 log.info(sm.getString("standardService.stop.name", this.name));
468 setState(LifecycleState.STOPPING);
469
470 // Stop our defined Container second
471 if (engine != null) {
472 synchronized (engine) {
473 engine.stop();
474 }
475 }
476
477 // Now stop the connectors
478 synchronized (connectorsLock) {
479 for (Connector connector: connectors) {
480 if (!LifecycleState.STARTED.equals(
481 connector.getState())) {
482 // Connectors only need stopping if they are currently
483 // started. They may have failed to start or may have been
484 // stopped (e.g. via a JMX call)
485 continue;
486 }
487 connector.stop();
488 }
489 }
490
491 // If the Server failed to start, the mapperListener won't have been
492 // started
493 if (mapperListener.getState() != LifecycleState.INITIALIZED) {
494 mapperListener.stop();
495 }
496
497 synchronized (executors) {
498 for (Executor executor: executors) {
499 executor.stop();
500 }
501 }
502
503 }
504
505
506 /**
507 * Invoke a pre-startup initialization. This is used to allow connectors
508 * to bind to restricted ports under Unix operating environments.
509 */
510 @Override
511 protected void initInternal() throws LifecycleException {
512
513 super.initInternal();
514
515 if (engine != null) {
516 engine.init();
517 }
518
519 // Initialize any Executors
520 for (Executor executor : findExecutors()) {
521 if (executor instanceof JmxEnabled) {
522 ((JmxEnabled) executor).setDomain(getDomain());
523 }
524 executor.init();
525 }
526
527 // Initialize mapper listener
528 mapperListener.init();
529
530 // Initialize our defined Connectors
531 synchronized (connectorsLock) {
532 for (Connector connector : connectors) {
533 connector.init();
534 }
535 }
536 }
537
538
539 @Override
540 protected void destroyInternal() throws LifecycleException {
541 mapperListener.destroy();
542
543 // Destroy our defined Connectors
544 synchronized (connectorsLock) {
545 for (Connector connector : connectors) {
546 connector.destroy();
547 }
548 }
549
550 // Destroy any Executors
551 for (Executor executor : findExecutors()) {
552 executor.destroy();
553 }
554
555 if (engine != null) {
556 engine.destroy();
557 }
558
559 super.destroyInternal();
560 }
561
562
563 /**
564 * Return the parent class loader for this component.
565 */
566 @Override
567 public ClassLoader getParentClassLoader() {
568 if (parentClassLoader != null)
569 return parentClassLoader;
570 if (server != null) {
571 return server.getParentClassLoader();
572 }
573 return ClassLoader.getSystemClassLoader();
574 }
575
576
577 /**
578 * Set the parent class loader for this server.
579 *
580 * @param parent The new parent class loader
581 */
582 @Override
583 public void setParentClassLoader(ClassLoader parent) {
584 ClassLoader oldParentClassLoader = this.parentClassLoader;
585 this.parentClassLoader = parent;
586 support.firePropertyChange("parentClassLoader", oldParentClassLoader,
587 this.parentClassLoader);
588 }
589
590
591 @Override
592 protected String getDomainInternal() {
593 String domain = null;
594 Container engine = getContainer();
595
596 // Use the engine name first
597 if (engine != null) {
598 domain = engine.getName();
599 }
600
601 // No engine or no engine name, use the service name
602 if (domain == null) {
603 domain = getName();
604 }
605
606 // No service name, return null which will trigger the use of the
607 // default
608 return domain;
609 }
610
611
612 @Override
613 public final String getObjectNameKeyProperties() {
614 return "type=Service";
615 }
616 }
617