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