Merge branch 'release/5.17.0'
[taxeditor.git] / eu.etaxonomy.taxeditor.webapp / src / main / java / eu / etaxonomy / taxeditor / webapp / CdmServer.java
1 /**
2 * Copyright (C) 2014 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.taxeditor.webapp;
10
11 import java.io.File;
12 import java.io.IOException;
13 import java.net.ServerSocket;
14 import java.net.URI;
15 import java.net.URISyntaxException;
16 import java.net.URL;
17
18 import javax.management.InstanceNotFoundException;
19 import javax.management.MBeanException;
20 import javax.management.MBeanServerConnection;
21 import javax.management.MalformedObjectNameException;
22 import javax.management.ObjectName;
23 import javax.management.ReflectionException;
24 import javax.management.remote.JMXConnector;
25 import javax.management.remote.JMXConnectorFactory;
26 import javax.management.remote.JMXServiceURL;
27
28 import org.apache.log4j.Logger;
29 import org.eclipse.core.runtime.FileLocator;
30 import org.eclipse.core.runtime.Platform;
31 import org.eclipse.jetty.server.Server;
32 import org.eclipse.jetty.util.StringUtil;
33 import org.eclipse.jetty.util.preventers.AppContextLeakPreventer;
34 import org.eclipse.jetty.webapp.WebAppContext;
35 import org.osgi.framework.Bundle;
36
37 /**
38 * (Singleton) Server instance which manages a compatible cdmlib-webapp-war.
39 * This is done by launching a jetty instance (using jetty-runner) as an
40 * executed process. The choice of the external process over a more
41 * preferable 'embedded jetty' instance is due to problems arising from the
42 * class loading of classes (e.g. from hibernate core) which are needed
43 * for both the webapp as well as the remoting client.
44 *
45 * @author cmathew
46 * @date 23 Sep 2014
47 */
48 public class CdmServer {
49
50 public static final Logger logger = Logger.getLogger(CdmServer.class);
51
52 //TODO use the constants defined in eu.etaxonomy.cdm.opt.config.DataSourceConfigurer
53 private static final String ATTRIBUTE_FORCE_SCHEMA_CREATE = "cdm.forceSchemaCreate";
54 private static final String ATTRIBUTE_FORCE_SCHEMA_UPDATE = "cdm.forceSchemaUpdate";
55 private static final String ATTRIBUTE_DATASOURCE_NAME = "cdm.datasource";
56 private static final String CDM_BEAN_DEFINITION_FILE = "cdm.beanDefinitionFile";
57
58 //see eu.etaxonomy.cdm.server.Bootloader.SPRING_PROFILES_ACTIVE
59 private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";
60
61
62 private final String dataSourceName;
63 private final String host = "127.0.0.1";
64 private int httpPort = 9090;
65 private final String contextPath = "/";
66
67 private File warFile;
68 private Server server;
69
70 private boolean forceSchemaCreate = false;
71 private boolean forceSchemaUpdate = false;
72
73 public CdmServer(String dataSourceName, File dataSourcesFile) throws CdmEmbeddedServerException {
74 if(StringUtil.isBlank(dataSourceName)) {
75 throw new CdmEmbeddedServerException("DataSource name is not valid");
76 }
77
78 if(dataSourcesFile == null || !dataSourcesFile.exists()) {
79 throw new CdmEmbeddedServerException("DataSource config file does not exist");
80 }
81 this.dataSourceName = dataSourceName;
82 Bundle bundle = Platform.getBundle("eu.etaxonomy.taxeditor.webapp");
83 URL warURL = bundle.getEntry("lib/cdmlib-remote-webapp.war");
84
85 try {
86 URL resolvedWarURL = FileLocator.toFileURL(warURL);
87 // We need to use the 3-arg constructor of URI in order to properly escape file system chars
88 URI resolvedURI = new URI(resolvedWarURL.getProtocol(), resolvedWarURL.getPath(), null);
89 warFile = new File(resolvedURI);
90 System.out.println("war url : " + warFile.getAbsolutePath());
91
92 if(warFile == null || !warFile.exists()) {
93 throw new CdmEmbeddedServerException("Cdmlib War file does not exist");
94 }
95 } catch (URISyntaxException use) {
96 throw new CdmEmbeddedServerException(use);
97 } catch (IOException ioe) {
98 throw new CdmEmbeddedServerException(ioe);
99 }
100
101 System.setProperty(SPRING_PROFILES_ACTIVE, "remoting");
102 System.setProperty(CDM_BEAN_DEFINITION_FILE, dataSourcesFile.getAbsolutePath());
103 System.setProperty(ATTRIBUTE_DATASOURCE_NAME, dataSourceName);
104
105 httpPort = findFreePort();
106
107 logger.warn("Starting server on port : " + httpPort);
108
109 server = new Server(httpPort);
110
111 server.addBean(new AppContextLeakPreventer());
112
113 WebAppContext webapp = new WebAppContext();
114 webapp.setContextPath(contextPath);
115 webapp.setWar(warFile.getAbsolutePath());
116 webapp.setThrowUnavailableOnStartupException(true);
117
118 server.setHandler(webapp);
119 }
120
121 public String getDataSourceName() {
122 return dataSourceName;
123 }
124 public String getHost() {
125 return host;
126 }
127
128 public int getPort() {
129 return httpPort;
130 }
131
132 public String getContextPath() {
133 return contextPath;
134 }
135
136 private static int findFreePort() throws CdmEmbeddedServerException {
137 ServerSocket socket = null;
138 try {
139 socket = new ServerSocket(0);
140 socket.setReuseAddress(true);
141 int port = socket.getLocalPort();
142 try {
143 socket.close();
144 } catch (IOException e) {
145
146 }
147 return port;
148 } catch (IOException e) {
149 } finally {
150 if (socket != null) {
151 try {
152 socket.close();
153 } catch (IOException e) {
154 }
155 }
156 }
157 throw new CdmEmbeddedServerException("Could not find a free TCP/IP port to start embedded Jetty HTTP Server on");
158 }
159
160 public void start(ICDMServerError cdmServerError) throws CdmEmbeddedServerException {
161 start(true, cdmServerError);
162 }
163
164 public void start(boolean wait, final ICDMServerError cdmServerError) throws CdmEmbeddedServerException {
165
166 if(server == null) {
167 throw new CdmEmbeddedServerException("Server is already disposed");
168 }
169
170 if(server.isStarting()) {
171 throw new CdmEmbeddedServerException("Server is starting");
172 }
173
174 if(server.isStarted()) {
175 throw new CdmEmbeddedServerException("Server has started");
176 }
177
178 if(server.isRunning()) {
179 throw new CdmEmbeddedServerException("Server is running");
180 }
181
182 if(server.isStopping()) {
183 throw new CdmEmbeddedServerException("Server is currently stopping. Please re-try in about 10 seconds");
184 }
185
186 Thread serverThread = new Thread() {
187
188 @Override
189 public void run() {
190 try {
191 if(isForceSchemaCreate()) {
192 System.setProperty(ATTRIBUTE_FORCE_SCHEMA_CREATE, "true");
193 }else if (isForceSchemaUpdate()){
194 System.setProperty(ATTRIBUTE_FORCE_SCHEMA_UPDATE, "true");
195 }
196 server.start();
197 server.join();
198 } catch (Throwable t) {
199 //wait for 1sec to get the right order of login dialog and error
200 //message when connection fails
201 try {
202 Thread.sleep(1000);
203 } catch (InterruptedException e) {
204 }
205 Throwable cause = t;
206 while(cause != null) {
207
208 // pure string comparison to avoid dependencies to hibernate or cdmlib-persistance in the taxeditor
209 if(cause.getClass().getSimpleName().equals("SchemaExtractionException") || cause.getClass().getSimpleName().equals("CdmDatabaseException") ) {
210 logger.debug(cause.getClass().getName() + " detected which indicates missing or corrupt schema");
211 cause = null;
212 } else {
213 cause = cause.getCause();
214 }
215 }
216 cdmServerError.handleError(new RuntimeException("Error during CDM server startup", t));
217 }
218 }
219 };
220
221 serverThread.start();
222
223 if(wait) {
224 while(!server.isStarted()) {}
225 }
226 }
227
228 public boolean isAlive() {
229 return server.isRunning() || server.isStarting() || server.isStarted();
230 }
231
232 public boolean isStarted() {
233 return server.isStarted();
234 }
235
236 public boolean isFailed() {
237 return server.isFailed();
238 }
239
240 public void stop() throws Exception {
241 server.stop();
242 server.destroy();
243 server = null;
244 }
245
246 public static void stopServerViaJMX(int jmxPort) throws CdmEmbeddedServerException {
247 String JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:" + jmxPort + "/jmxrmi";
248 logger.warn("Shutting down Jetty instance ... ");
249
250 try {
251 JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(JMX_URL), null);
252 connector.connect(null);
253 MBeanServerConnection connection = connector.getMBeanServerConnection();
254 ObjectName objectName = new ObjectName("org.eclipse.jetty.server:type=server,id=0");
255 connection.invoke(objectName, "stop", null, null);
256 logger.warn("Shutdown command sent");
257 } catch (InstanceNotFoundException e) {
258 throw new CdmEmbeddedServerException(e);
259 } catch (MBeanException e) {
260 throw new CdmEmbeddedServerException(e);
261 } catch (ReflectionException e) {
262 throw new CdmEmbeddedServerException(e);
263 } catch (IOException e) {
264 throw new CdmEmbeddedServerException(e);
265 } catch (MalformedObjectNameException e) {
266 throw new CdmEmbeddedServerException(e);
267 }
268 }
269
270 public boolean isForceSchemaCreate() {
271 return forceSchemaCreate;
272 }
273
274 public void setForceSchemaCreate(boolean forceSchemaCreate) {
275 this.forceSchemaCreate = forceSchemaCreate;
276 }
277
278 public boolean isForceSchemaUpdate() {
279 return forceSchemaUpdate;
280 }
281 public void setForceSchemaUpdate(boolean forceSchemaUpdate) {
282 this.forceSchemaUpdate = forceSchemaUpdate;
283 }
284 }