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