001/*
002 * Copyright 2013 Atteo.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.atteo.moonshine.tomcat;
017
018import java.io.IOException;
019import java.nio.file.Files;
020import java.nio.file.Paths;
021import java.util.ArrayList;
022import java.util.List;
023
024import javax.xml.bind.annotation.XmlElement;
025import javax.xml.bind.annotation.XmlElementWrapper;
026import javax.xml.bind.annotation.XmlRootElement;
027
028import org.apache.catalina.Container;
029import org.apache.catalina.Context;
030import org.apache.catalina.LifecycleException;
031import static org.apache.catalina.LifecycleState.STARTED;
032import org.apache.catalina.connector.Connector;
033import org.apache.catalina.core.StandardHost;
034import org.apache.catalina.startup.Tomcat;
035import org.atteo.evo.config.XmlDefaultValue;
036import org.atteo.moonshine.services.Service;
037import org.atteo.moonshine.webserver.WebServerAddress;
038import org.atteo.moonshine.webserver.WebServerService;
039
040import com.google.inject.AbstractModule;
041import com.google.inject.Module;
042import com.google.inject.servlet.GuiceFilter;
043
044/**
045 * Starts Tomcat.
046 *
047 * <p>
048 * Tomcat support is currently very basic. You should choose Jetty most of the time.
049 * </p>
050 */
051@XmlRootElement(name = "tomcat")
052public class TomcatService extends WebServerService {
053    /**
054     * Tomcat base directory.
055     */
056    @XmlElement
057    @XmlDefaultValue("${dataHome}/tomcat")
058    private String baseDir;
059
060    @XmlElementWrapper(name = "connectors")
061    @XmlElement(name = "connector")
062    private List<TomcatConnectorConfig> connectors = new ArrayList<TomcatConnectorConfig>() {
063        private static final long serialVersionUID = 1L;
064
065        {
066            add(new TomcatConnectorConfig());
067        }
068    };
069
070    /**
071     * Tomcat default host.
072     * <p>
073     * If not provided will be automatically set to the name of the first host in {@link #hosts}.
074     * </p>
075     */
076    @XmlElement
077    private String defaultHost = null;
078
079    @XmlElementWrapper(name = "hosts")
080    @XmlElement(name = "host")
081    private List<HostConfig> hosts = new ArrayList<HostConfig>() {
082        {
083            add(new HostConfig());
084        }
085    };
086
087    private Tomcat tomcat = null;
088
089    @Override
090    public Module configure() {
091        return new AbstractModule() {
092            @Override
093            protected void configure() {
094                bind(GuiceFilter.class);
095
096                try {
097                    Files.createDirectories(Paths.get(baseDir));
098                } catch (IOException e) {
099                    throw new RuntimeException(e);
100                }
101
102                bind(WebServerAddress.class).toInstance(new WebServerAddress() {
103                    @Override
104                    public int getPort() {
105                        return tomcat.getConnector().getLocalPort();
106                    }
107
108                    @Override
109                    public String getHost() {
110                        return tomcat.getHost().getName();
111                    }
112
113                    @Override
114                    public String getUrl() {
115                        String host = getHost();
116                        if (host == null) {
117                            host = "localhost";
118                        }
119                        return tomcat.getConnector().getScheme() + "://" + host + ":" + getPort();
120                    }
121                });
122            }
123        };
124    }
125
126    private void init() {
127        tomcat = new Tomcat();
128        tomcat.setBaseDir(baseDir);
129        if (defaultHost != null) {
130            tomcat.getEngine().setDefaultHost(defaultHost);
131        }
132
133        for (HostConfig hostConfig : hosts) {
134            StandardHost host = new StandardHost();
135            host.setAppBase(hostConfig.getAppBase());
136            host.setName(hostConfig.getName());
137            if (defaultHost == null) {
138                defaultHost = hostConfig.getName();
139                tomcat.getEngine().setDefaultHost(defaultHost);
140            }
141
142            for (ContextConfig contextConfig : hostConfig.getContexts()) {
143                Context context = tomcat.addContext(host, contextConfig.getPath(), contextConfig.getBaseDir());
144                contextConfig.configure(context);
145            }
146
147            tomcat.getEngine().addChild(host);
148            tomcat.setHost(host);
149        }
150
151        for (TomcatConnectorConfig connectorConfig : connectors) {
152            Connector connector = new Connector(connectorConfig.getProtocol());
153            connector.setPort(connectorConfig.getPort());
154
155            tomcat.setConnector(connector);
156            tomcat.getService().addConnector(connector);
157        }
158    }
159
160    @Override
161    public void start() {
162        if (tomcat == null) {
163            init();
164        }
165        try {
166            tomcat.start();
167            if (tomcat.getConnector().getState() != STARTED) {
168                throw new RuntimeException("Cannot start Tomcat, check logs");
169            }
170            for (Container container : tomcat.getHost().findChildren()) {
171                if (container.getState() != STARTED) {
172                    throw new RuntimeException("Cannot start Tomcat, check logs");
173                }
174            }
175        } catch (LifecycleException e) {
176            throw new RuntimeException(e);
177        }
178    }
179
180    @Override
181    public void stop() {
182        try {
183            tomcat.stop();
184        } catch (LifecycleException e) {
185            throw new RuntimeException(e);
186        }
187    }
188
189    @Override
190    public Iterable<? extends Service> getSubServices() {
191        List<Service> result = new ArrayList<>();
192        for (TomcatConnectorConfig connector : connectors) {
193            if (connector instanceof Service) {
194                result.add((Service) connector);
195            }
196        }
197
198        for (HostConfig host : hosts) {
199            for (ContextConfig context : host.getContexts()) {
200                if (context instanceof Service) {
201                    result.add((Service) context);
202                }
203            }
204        }
205        return result;
206    }
207
208}