ref #9359 upgrade TaxEditor to log4j2
[taxeditor.git] / eu.etaxonomy.taxeditor.cdmlib / src / main / java / eu / etaxonomy / taxeditor / remoting / source / CdmServerInfo.java
1 /**
2 * Copyright (C) 2015 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.remoting.source;
10
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileOutputStream;
14 import java.io.IOException;
15 import java.net.InetSocketAddress;
16 import java.net.Socket;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.List;
21
22 import org.apache.commons.lang3.StringUtils;
23 import org.apache.http.HttpEntity;
24 import org.apache.http.HttpResponse;
25 import org.apache.http.client.ClientProtocolException;
26 import org.apache.http.client.ResponseHandler;
27 import org.apache.http.client.config.RequestConfig;
28 import org.apache.http.client.methods.HttpGet;
29 import org.apache.http.impl.client.CloseableHttpClient;
30 import org.apache.http.impl.client.HttpClientBuilder;
31 import org.apache.http.util.EntityUtils;
32 import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
33 import org.json.JSONArray;
34 import org.json.JSONException;
35 import org.json.JSONObject;
36
37 import com.fasterxml.jackson.core.type.TypeReference;
38 import com.fasterxml.jackson.databind.ObjectMapper;
39
40 import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteConfiguration;
41 import eu.etaxonomy.cdm.api.application.CdmApplicationState;
42 import eu.etaxonomy.cdm.common.CdmUtils;
43 import eu.etaxonomy.cdm.config.CdmSourceException;
44 import eu.etaxonomy.cdm.config.ConfigFileUtil;
45 import eu.etaxonomy.cdm.config.ICdmSource;
46 import eu.etaxonomy.cdm.database.CdmPersistentDataSource;
47 import eu.etaxonomy.cdm.database.ICdmDataSource;
48 import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
49 import eu.etaxonomy.taxeditor.remoting.server.CdmServerException;
50 import eu.etaxonomy.taxeditor.remoting.server.CdmServerUtils;
51
52 /**
53 * @author cmathew
54 * @date 20 Jan 2015
55 */
56 public class CdmServerInfo {
57
58 public static final Logger logger = LogManager.getLogger(CdmServerInfo.class);
59
60 protected final static String CDMSERVER_PREFIX = "cdmserver/";
61 private final static String NAME_PRODUCTION = "cybertaxonomy.org";
62 private final static String SERVER_PRODUCTION = "api.cybertaxonomy.org";
63
64 private final static String NAME_DEMO_1 = "demo I";
65 private final static String SERVER_DEMO_1 = "160.45.63.230";
66
67 public final static String SERVER_LOCALHOST = "localhost";
68 private final static String NAME_LOCALHOST = "localhost";
69 public final static String NAME_LOCALHOST_MGD = "localhost mgd.";
70
71 private final static String NAME_LOCALHOST_DEV = "localhost-dev";
72 private final static String NAME_INSTANCE_LOCALHOST_DEV = "local-dev";
73 private final static String SERVER_LOCALHOST_DEV = "localhost";
74 private final static String BASEPATH_LOCALHOST_DEV = "";
75
76 public final static int NULL_PORT = -1;
77 public final static String NULL_PORT_STRING = "N/A";
78
79 protected static final String CDM_REMOTE_SERVERS_CONFIG_FILE = "cdm_remote_servers.json";
80
81
82 private final String name;
83 private final String server;
84 private int port;
85 protected final List<CdmInstanceInfo> instances;
86
87 private String cdmlibServicesVersion = "";
88 private String cdmlibServicesLastModified = "";
89
90 private String prefix = "";
91
92 private boolean ignoreCdmLibVersion = false;
93
94
95 public CdmServerInfo(CdmServerInfoConfig parameterObject) {
96 this.name = parameterObject.getName();
97 this.server = parameterObject.getServer();
98 this.port = parameterObject.getPort();
99 this.prefix = parameterObject.getPrefix();
100 this.ignoreCdmLibVersion = parameterObject.isIgnoreCdmLibVersion();
101 this.instances = new ArrayList<>();
102 }
103
104 public CdmInstanceInfo addInstance(String name, ICdmDataSource dataSource) {
105 CdmInstanceInfo cii = new CdmInstanceInfo(name, dataSource);
106 instances.add(cii);
107 return cii;
108 }
109
110 public CdmInstanceInfo addInstance(String name, String basePath) {
111 CdmInstanceInfo cii = new CdmInstanceInfo(name, basePath);
112 instances.add(cii);
113 return cii;
114 }
115
116 public boolean isLocalhost() {
117 return name.startsWith(SERVER_LOCALHOST);
118 }
119
120 public boolean isLocalhostMgd() {
121 return NAME_LOCALHOST_MGD.equals(name);
122 }
123
124 public String getCdmlibServicesVersion() {
125 return cdmlibServicesVersion;
126 }
127
128 public String getCdmlibLastModified() {
129 return cdmlibServicesLastModified;
130 }
131
132 public void refreshInstances() throws CdmServerException {
133 instances.clear();
134 if(isLocalhostMgd()) {
135 addInstancesFromDataSourcesConfig();
136 } else {
137 addInstancesViaHttp();
138 }
139
140
141 Collections.sort(instances, new Comparator<CdmInstanceInfo>() {
142 @Override
143 public int compare(CdmInstanceInfo cii1, CdmInstanceInfo cii2){
144 return cii1.getName().toString().compareTo(cii2.getName().toString());
145 }
146 });
147 }
148
149 public void updateInfo() throws CdmServerException {
150
151 String url = guessProtocol() + "://" + server + ":" + String.valueOf(port) + "/" + prefix + "info.jsp";
152 String responseBody = getResponse(url);
153 if(responseBody != null) {
154 try {
155 JSONObject info = new JSONObject(responseBody);
156 cdmlibServicesVersion = info.getString("cdmlibServicesVersion");
157 cdmlibServicesLastModified = info.getString("cdmlibServicesLastModified");
158 } catch (JSONException e) {
159 throw new CdmServerException(e);
160 }
161 }
162 }
163
164 String guessProtocol() {
165 return port == 443 ? "https" : "http";
166 }
167
168 public void addInstancesViaHttp() throws CdmServerException {
169 updateInfo();
170 String url = guessProtocol() + "://" + server + ":" + String.valueOf(port) + "/" + prefix + "instances.jsp";
171 String responseBody = getResponse(url);
172 if(responseBody != null) {
173 try {
174 JSONArray array = new JSONArray(responseBody);
175 for(int i=0;i<array.length();i++) {
176 JSONObject instance = (JSONObject)array.get(i);
177 if(instance != null) {
178 JSONObject conf = (JSONObject)instance.get("configuration");
179 if(conf != null) {
180 String instanceName = conf.getString("instanceName");
181 // we need to remove the first (char) forward slash from
182 // the base path
183 String basePath = conf.getString("basePath").substring(1);
184 addInstance(instanceName, basePath);
185 logger.info("Added instance with name : " + instanceName + ", basePath : " + basePath);
186 }
187 }
188 }
189 } catch (JSONException e) {
190 throw new CdmServerException(e);
191 }
192 }
193 }
194
195 private String getResponse(String url) throws CdmServerException {
196
197 RequestConfig.Builder requestBuilder = RequestConfig.custom();
198 requestBuilder.setConnectTimeout(CdmApplicationRemoteConfiguration.HTTP_READ_TIMEOUT_MIN);
199 requestBuilder.setConnectionRequestTimeout(CdmApplicationRemoteConfiguration.HTTP_READ_TIMEOUT_MIN);
200 requestBuilder.setSocketTimeout(CdmApplicationRemoteConfiguration.HTTP_READ_TIMEOUT_MIN);
201 CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build()).build();
202
203 HttpGet httpGet = new HttpGet(url);
204
205 logger.info("Executing request " + httpGet.getRequestLine());
206
207 // Create a custom response handler
208 ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
209
210 @Override
211 public String handleResponse(
212 final HttpResponse response) throws ClientProtocolException, IOException {
213 int status = response.getStatusLine().getStatusCode();
214 if (status >= 200 && status < 300) {
215 HttpEntity entity = response.getEntity();
216 return entity != null ? EntityUtils.toString(entity) : null;
217 } else {
218 throw new ClientProtocolException("Unexpected response status: " + status);
219 }
220 }
221
222 };
223 String responseBody = null;
224 try {
225 responseBody = client.execute(httpGet, responseHandler);
226 } catch (ClientProtocolException e) {
227 throw new CdmServerException(e);
228 } catch (IOException e) { // java.net.ConnectException: Connection refused, java.net.SocketTimeoutException: Read timed out
229 throw new CdmServerException(e);
230 } finally{
231 try {
232 client.close();
233 client = null;
234 } catch (IOException e) {
235 // IGNORE //
236 }
237 }
238 return responseBody;
239 }
240
241 public void addInstancesFromDataSourcesConfig() {
242 for(ICdmDataSource dataSource : CdmPersistentDataSource.getAllDataSources()){
243 String datasourceNCName = CdmServerUtils.xmlNCNamefrom(dataSource.getName());
244 logger.info("Adding local instance " + dataSource.getName() + " as " + datasourceNCName);
245 addInstance(datasourceNCName, dataSource);
246 }
247 }
248
249 public String toString(String instanceName, int port) {
250 return server + ":" + String.valueOf(port) + "/" + instanceName;
251 }
252
253 public CdmInstanceInfo getInstanceFromName(String instanceName) {
254 if(instanceName == null) {
255 return null;
256 }
257
258 for(CdmInstanceInfo instance : instances) {
259 if(instance.getName() != null && instance.getName().equals(instanceName)) {
260 return instance;
261 }
262 }
263 return null;
264 }
265
266 public ICdmRemoteSource getCdmRemoteSource(CdmInstanceInfo instance, int port) {
267 if(instance != null) {
268 if (instance.getDataSource()!=null){
269 return CdmRemoteLocalhostSource.NewInstance(name,
270 server,
271 port,
272 instance.getDataSource(),
273 instance.name
274 );
275 }else{
276 return CdmRemoteSource.NewInstance(name,
277 server,
278 port,
279 instance.getBasePath()
280 );
281 }
282 }
283 return null;
284 }
285
286 public boolean pingServer() throws CdmServerException, IOException {
287 if(isLocalhostMgd()) {
288 return true;
289 }
290 Socket s = new Socket();
291 s.connect(new InetSocketAddress(server, port), CdmApplicationRemoteConfiguration.HTTP_READ_TIMEOUT_MIN);
292 s.close();
293 logger.info("[CDM-Server] Available @ " + server + ":" + port );
294 updateInfo();
295 return true;
296 }
297
298 public boolean pingInstance(CdmInstanceInfo instance, int port) throws CdmServerException {
299 ICdmSource cdmRemoteSource = getCdmRemoteSource(instance, port);
300 try {
301 if(cdmRemoteSource != null && cdmRemoteSource.checkConnection()) {
302 logger.info("[CDM-Server] Running @ " + server + ":" + port + " for instance " + instance);
303 return true;
304 }
305 } catch (CdmSourceException e) {
306 logger.error(e.getMessage(), e);
307 throw new CdmServerException(e);
308 }
309
310 return false;
311 }
312
313 public int compareDbSchemaVersion(CdmInstanceInfo instance, int port) throws CdmServerException {
314
315 ICdmSource remoteSource = getCdmRemoteSource(instance, port);
316 String remoteDbSchemaVersion;
317 try {
318 remoteDbSchemaVersion = remoteSource.getDbSchemaVersion();
319 } catch (CdmSourceException e) {
320 throw new CdmServerException(e);
321 }
322
323 if(remoteDbSchemaVersion != null) {
324 return CdmMetaData.compareVersion(remoteDbSchemaVersion, CdmMetaData.getDbSchemaVersion(), 3, null);
325 } else {
326 throw new CdmServerException("Cannot determine editor db schema version");
327 }
328 }
329
330 public int compareCdmlibServicesVersion() throws CdmSourceException {
331
332 String serverVersion = cdmlibServicesVersion;
333 String serverCdmlibLastModified = cdmlibServicesLastModified;
334 if(ignoreCdmLibVersion) {
335 return 0;
336 } else {
337 return compareCdmlibServicesVersion(serverVersion, serverCdmlibLastModified);
338 }
339 }
340
341 public static int compareCdmlibServicesVersion(String serverVersion, String serverCdmlibLastModified) throws CdmSourceException {
342
343 String editorVersion = CdmApplicationState.getCdmlibVersion();
344 String editorCdmlibLastModified = CdmApplicationState.getCdmlibLastModified();
345
346 int result = 0;
347 if(StringUtils.isBlank(serverVersion) || StringUtils.isBlank(editorVersion)) {
348 throw new CdmSourceException("cdmlib-services server or editor version is empty");
349 }
350
351 String[] serverVersionSplit = serverVersion.split("\\.");
352 String[] editorVersionSplit = editorVersion.split("\\.");
353
354 if(serverVersionSplit.length < 3 || editorVersionSplit.length < 3 || serverVersionSplit.length > 4 || editorVersionSplit.length > 4) {
355 throw new CdmSourceException("cdmlib-services server or editor version is invalid");
356 }
357
358 Integer serverVersionPart;
359 Integer editorVersionPart;
360
361 for(int i=0 ; i<3 ; i++) {
362 serverVersionPart = Integer.valueOf(serverVersionSplit[i]);
363 editorVersionPart = Integer.valueOf(editorVersionSplit[i]);
364
365 int partCompare = serverVersionPart.compareTo(editorVersionPart);
366 if (partCompare != 0){
367 return partCompare;
368 }
369 }
370 // at this point major, minor and patch versions are matching
371
372 if(StringUtils.isBlank(serverCdmlibLastModified) || StringUtils.isBlank(editorCdmlibLastModified)) {
373 throw new CdmSourceException("cdmlib-services server or editor version is empty");
374 }
375
376 String cdmServerIgnoreVersion = System.getProperty("cdm.server.version.lm.ignore");
377 if(StringUtils.isBlank(cdmServerIgnoreVersion) || !cdmServerIgnoreVersion.equals("true")) {
378 Long serverLastModified = Long.valueOf(serverCdmlibLastModified);
379 Long editorLastModified = Long.valueOf(editorCdmlibLastModified);
380 return serverLastModified.compareTo(editorLastModified);
381 }
382
383 return result;
384 }
385
386 public static List<CdmServerInfo> getCdmServers(boolean isLocal) {
387 List<CdmServerInfoConfig> configList;
388 File file = new File(ConfigFileUtil.perUserCdmFolderFallback(), CDM_REMOTE_SERVERS_CONFIG_FILE);
389 if (file.exists()){
390 configList = loadFromConfigFile(file);
391 }else{
392 configList = loadFromConfigFile(new File(ConfigFileUtil.perUserCdmFolderFallback(), CDM_REMOTE_SERVERS_CONFIG_FILE));
393 }
394 List<CdmServerInfo> serverInfoList = new ArrayList<>(configList.size());
395 for(CdmServerInfoConfig config : configList) {
396 serverInfoList.add(new CdmServerInfo(config));
397 }
398 // The local host managed server must not be in the config file, this should be moved and only added when plugin is installed
399 if (isLocal){
400 CdmServerInfoConfig localHostManagedConfig = new CdmServerInfoConfig(NAME_LOCALHOST_MGD, SERVER_LOCALHOST, NULL_PORT, CDMSERVER_PREFIX, false);
401 serverInfoList.add(new CdmServerInfo(localHostManagedConfig));
402 }
403
404 return serverInfoList;
405 }
406
407 protected static List<CdmServerInfoConfig> loadFromConfigFile(File file) {
408
409 List<CdmServerInfoConfig> serverList = null;
410
411 ObjectMapper mapper = new ObjectMapper();
412
413 if(!file.exists()) {
414 logger.info("The remote server config file '" + file.getAbsolutePath() +
415 "' does not yet exist, it will be created based on the defaults.");
416 try {
417 serverList = createDefaultServerConfigList();
418 mapper.writerWithDefaultPrettyPrinter().writeValue(new FileOutputStream(file), serverList);
419
420 } catch (IOException e) {
421 throw new RuntimeException(e);
422 }
423 } else {
424 try {
425 serverList = mapper.readValue(new FileInputStream(file), new TypeReference<List<CdmServerInfoConfig>>(){});
426 } catch (IOException e) {
427 throw new RuntimeException(e);
428 }
429 }
430
431 return serverList;
432 }
433
434 private static List<CdmServerInfoConfig> createDefaultServerConfigList() {
435
436 List<CdmServerInfoConfig> serverInfoList = new ArrayList<>();
437 serverInfoList.add(new CdmServerInfoConfig(NAME_PRODUCTION, SERVER_PRODUCTION, 443, "", false));
438 serverInfoList.add(new CdmServerInfoConfig(NAME_DEMO_1, SERVER_DEMO_1, 80, CDMSERVER_PREFIX, false));
439 serverInfoList.add(new CdmServerInfoConfig(NAME_LOCALHOST, SERVER_LOCALHOST, 8080, CDMSERVER_PREFIX, true));
440 return serverInfoList;
441 }
442
443 public String getName() {
444 return name;
445 }
446
447 public String getServer() {
448 return server;
449 }
450
451 public int getPort() {
452 return port;
453 }
454
455 public void setPort(int port) {
456 this.port = port;
457 }
458
459 public List<CdmInstanceInfo> getInstances() throws CdmServerException {
460 if(instances.isEmpty()) {
461 refreshInstances();
462 }
463 return instances;
464 }
465
466 public static ICdmSource getDevServerRemoteSource() {
467 String value = System.getProperty("cdm.server.dev.port");
468 boolean available = false;
469 CdmInstanceInfo devInstance = null;
470 if(CdmUtils.isNotBlank(value)) {
471 int devPort = Integer.valueOf(value);
472 CdmServerInfo devCii = new CdmServerInfo(new CdmServerInfoConfig(NAME_LOCALHOST_DEV, SERVER_LOCALHOST_DEV, devPort, "", false));
473 try {
474 devInstance = devCii.addInstance(NAME_INSTANCE_LOCALHOST_DEV, BASEPATH_LOCALHOST_DEV);
475 available = devCii.pingInstance(devInstance, devPort);
476 if(available) {
477 logger.info("Will connect local development cdm instance: at port " + devPort);
478 return devCii.getCdmRemoteSource(devInstance, devPort);
479 }
480 } catch (CdmServerException e) {
481 logger.error("Can not connect to local development cdm instance at port " + devPort + ". "
482 + "Make sure the cdm instance is running and that the Spring profile \"remoting\" is "
483 + "activated (-Dspring.profiles.active=remoting)", e);
484 //TODO show message dialog only instead of throwing the exception to show the error
485 // dialog is not necessary in this situation
486 }
487 logger.error("local development cdm instance at port " + devPort + " is not accessible");
488 }
489 return null;
490 }
491
492 public class CdmInstanceInfo {
493
494 private final String name;
495
496 /**
497 * The full path of the instance including the the prefix (if any).
498 * E.g. for an EDIT instance this would be something like "cdmserver/remoting"
499 * For a managed local server this would simply be ""
500 */
501 private final String basePath;
502
503 private final ICdmDataSource dataSource;
504
505 public CdmInstanceInfo(String name, String basePath) {
506 this.name = name;
507 this.basePath = basePath;
508 this.dataSource = null;
509 }
510
511 public CdmInstanceInfo(String name, ICdmDataSource dataSource) {
512 this.name = name;
513 this.basePath = "";
514 this.dataSource = dataSource;
515 }
516
517 public String getName() {
518 return name;
519 }
520
521 public String getBasePath() {
522 return basePath;
523 }
524
525 public ICdmDataSource getDataSource() {
526 return dataSource;
527 }
528 }
529 }