Merge branch 'develop' into feature/cdm-4.7
[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.FileNotFoundException;
14 import java.io.FileOutputStream;
15 import java.io.IOException;
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.lang.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.HttpClient;
27 import org.apache.http.client.ResponseHandler;
28 import org.apache.http.client.methods.HttpGet;
29 import org.apache.http.impl.client.DefaultHttpClient;
30 import org.apache.http.params.HttpConnectionParams;
31 import org.apache.http.params.HttpParams;
32 import org.apache.http.util.EntityUtils;
33 import org.apache.log4j.Logger;
34 import org.json.JSONArray;
35 import org.json.JSONException;
36 import org.json.JSONObject;
37
38 import com.fasterxml.jackson.core.JsonParseException;
39 import com.fasterxml.jackson.core.type.TypeReference;
40 import com.fasterxml.jackson.databind.JsonMappingException;
41 import com.fasterxml.jackson.databind.ObjectMapper;
42
43 import eu.etaxonomy.cdm.api.application.CdmApplicationState;
44 import eu.etaxonomy.cdm.common.CdmUtils;
45 import eu.etaxonomy.cdm.config.CdmSourceException;
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 */
57 public class CdmServerInfo {
58 public static final Logger logger = Logger.getLogger(CdmServerInfo.class);
59
60 private 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 = "edit-WS 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 private 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 private 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 instances = new ArrayList<CdmInstanceInfo>();
102 }
103
104
105 public CdmInstanceInfo addInstance(String name, String basePath) {
106 String _basePath = basePath;
107 if(isLocalhostMgd()) {
108 _basePath = "";
109 }
110 CdmInstanceInfo cii = new CdmInstanceInfo(name, _basePath);
111 instances.add(cii);
112 return cii;
113
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 Collections.sort(instances, new Comparator<CdmInstanceInfo>() {
140 @Override
141 public int compare(CdmInstanceInfo cii1, CdmInstanceInfo cii2)
142 {
143 return cii1.getName().toString().compareTo(cii2.getName().toString());
144 }
145 });
146 }
147
148 public void updateInfo() throws CDMServerException {
149 String url = "http://" + server + ":" + String.valueOf(port) + "/" + prefix + "info.jsp";
150 String responseBody = getResponse(url);
151 if(responseBody != null) {
152 try {
153 JSONObject info = new JSONObject(responseBody);
154 cdmlibServicesVersion = info.getString("cdmlibServicesVersion");
155 cdmlibServicesLastModified = info.getString("cdmlibServicesLastModified");
156 } catch (JSONException e) {
157 throw new CDMServerException(e);
158 }
159 }
160 }
161
162 public void addInstancesViaHttp() throws CDMServerException {
163 updateInfo();
164 String url = "http://" + server + ":" + String.valueOf(port) + "/" + prefix + "instances.jsp";
165 String responseBody = getResponse(url);
166 if(responseBody != null) {
167 try {
168 JSONArray array = new JSONArray(responseBody);
169 for(int i=0;i<array.length();i++) {
170 JSONObject instance = (JSONObject)array.get(i);
171 if(instance != null) {
172 JSONObject conf = (JSONObject)instance.get("configuration");
173 if(conf != null) {
174 String instanceName = conf.getString("instanceName");
175 // we need to remove the first (char) forward slash from
176 // the base path
177 String basePath = conf.getString("basePath").substring(1);
178 addInstance(instanceName, basePath);
179 logger.info("Added instance with name : " + instanceName + ", basePath : " + basePath);
180 }
181 }
182 }
183 } catch (JSONException e) {
184 throw new CDMServerException(e);
185 }
186 }
187 }
188
189 private String getResponse(String url) throws CDMServerException {
190 HttpClient client = new DefaultHttpClient();
191 HttpParams params = client.getParams();
192
193 HttpConnectionParams.setConnectionTimeout(params, 5000);
194 HttpConnectionParams.setSoTimeout(params, 5000);
195
196 HttpGet httpGet = new HttpGet(url);
197
198 logger.info("Executing request " + httpGet.getRequestLine());
199
200 // Create a custom response handler
201 ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
202
203 @Override
204 public String handleResponse(
205 final HttpResponse response) throws ClientProtocolException, IOException {
206 int status = response.getStatusLine().getStatusCode();
207 if (status >= 200 && status < 300) {
208 HttpEntity entity = response.getEntity();
209 return entity != null ? EntityUtils.toString(entity) : null;
210 } else {
211 throw new ClientProtocolException("Unexpected response status: " + status);
212 }
213 }
214
215 };
216 String responseBody = null;
217 try {
218 responseBody = client.execute(httpGet, responseHandler);
219 } catch (ClientProtocolException e) {
220 throw new CDMServerException(e);
221 } catch (IOException e) {
222 throw new CDMServerException(e);
223 }
224 return responseBody;
225 }
226
227 public void addInstancesFromDataSourcesConfig() {
228 for(ICdmDataSource dataSource : CdmPersistentDataSource.getAllDataSources()){
229 String datasourceNCName = CDMServerUtils.xmlNCNamefrom(dataSource.getName());
230 logger.info("Adding local instance " + dataSource.getName() + " as " + datasourceNCName);
231 addInstance(datasourceNCName, datasourceNCName);
232 }
233 }
234
235 public String toString(String instanceName, int port) {
236 return server + ":" + String.valueOf(port) + "/" + instanceName;
237 }
238
239 public CdmInstanceInfo getInstanceFromName(String instanceName) {
240 if(instanceName == null) {
241 return null;
242 }
243
244 for(CdmInstanceInfo instance : instances) {
245 if(instance.getName() != null && instance.getName().equals(instanceName)) {
246 return instance;
247 }
248 }
249 return null;
250 }
251
252 public CdmRemoteSource getCdmRemoteSource(CdmInstanceInfo instance, int port) {
253 if(instance != null) {
254 return CdmRemoteSource.NewInstance(name,
255 server,
256 port,
257 instance.getBasePath()
258 );
259 }
260 return null;
261 }
262
263 public boolean pingServer() {
264 if(isLocalhostMgd()) {
265 return true;
266 }
267 try {
268 Socket s = new Socket(server, port);
269 logger.info("[CDM-Server] Available @ " + server + ":" + port );
270 updateInfo();
271 return true;
272 } catch (IOException ioe) {
273
274 } catch (CDMServerException e) {
275
276 }
277 return false;
278 }
279
280 public boolean pingInstance(CdmInstanceInfo instance, int port) throws CDMServerException {
281
282 ICdmRemoteSource crs = getCdmRemoteSource(instance, port);
283 try {
284 if(crs != null && crs.checkConnection()) {
285 logger.info("[CDM-Server] Running @ " + server + ":" + port + " for instance " + instance);
286 return true;
287 }
288 } catch (CdmSourceException e) {
289 throw new CDMServerException(e);
290 }
291
292 return false;
293 }
294
295 public int compareDbSchemaVersion(CdmInstanceInfo instance, int port) throws CDMServerException {
296
297 ICdmRemoteSource crs = getCdmRemoteSource(instance, port);
298 String dbSchemaVersion;
299 try {
300 dbSchemaVersion = crs.getDbSchemaVersion();
301 } catch (CdmSourceException e) {
302 throw new CDMServerException(e);
303 }
304
305
306 if(dbSchemaVersion != null) {
307 return CdmMetaData.compareVersion(dbSchemaVersion, CdmMetaData.getDbSchemaVersion(), 3, null);
308 } else {
309 throw new CDMServerException("Cannot determine editor db. schema version");
310 }
311 }
312
313 public int compareCdmlibServicesVersion() throws CdmSourceException {
314
315 String serverVersion = cdmlibServicesVersion;
316 String serverCdmlibLastModified = cdmlibServicesLastModified;
317 if(ignoreCdmLibVersion) {
318 return 0;
319 } else {
320 return compareCdmlibServicesVersion(serverVersion, serverCdmlibLastModified);
321 }
322 }
323
324
325 /**
326 * @param serverVersion
327 * @param editorVersion
328 * @throws CdmSourceException
329 */
330 public static int compareCdmlibServicesVersion(String serverVersion, String serverCdmlibLastModified) throws CdmSourceException {
331
332 String editorVersion = CdmApplicationState.getCdmlibVersion();
333 String editorCdmlibLastModified = CdmApplicationState.getCdmlibLastModified();
334
335 int result = 0;
336 if(StringUtils.isBlank(serverVersion) || StringUtils.isBlank(editorVersion)) {
337 throw new CdmSourceException("cdmlib-services server or editor version is empty");
338 }
339
340 String[] serverVersionSplit = serverVersion.split("\\.");
341 String[] editorVersionSplit = editorVersion.split("\\.");
342
343 if(serverVersionSplit.length < 3 || editorVersionSplit.length < 3 || serverVersionSplit.length > 4 || editorVersionSplit.length > 4) {
344 throw new CdmSourceException("cdmlib-services server or editor version is invalid");
345 }
346
347 Integer serverVersionPart;
348 Integer editorVersionPart;
349
350 for(int i=0 ; i<3 ; i++) {
351 serverVersionPart = Integer.valueOf(serverVersionSplit[i]);
352 editorVersionPart = Integer.valueOf(editorVersionSplit[i]);
353
354 int partCompare = serverVersionPart.compareTo(editorVersionPart);
355 if (partCompare != 0){
356 return partCompare;
357 }
358 }
359 // at this point major, minor and patch versions are matching
360
361 if(StringUtils.isBlank(serverCdmlibLastModified) || StringUtils.isBlank(editorCdmlibLastModified)) {
362 throw new CdmSourceException("cdmlib-services server or editor version is empty");
363 }
364
365 String cdmServerIgnoreVersion = System.getProperty("cdm.server.version.lm.ignore");
366 if(StringUtils.isBlank(cdmServerIgnoreVersion) || !cdmServerIgnoreVersion.equals("true")) {
367 Long serverLastModified = Long.valueOf(serverCdmlibLastModified);
368 Long editorLastModified = Long.valueOf(editorCdmlibLastModified);
369 return serverLastModified.compareTo(editorLastModified);
370 }
371
372 return result;
373 }
374
375
376
377 public static List<CdmServerInfo> getCdmServers() {
378 List<CdmServerInfoConfig> configList = loadFromConfigFile(new File(CdmUtils.PER_USER_CDM_FOLDER, CDM_REMOTE_SERVERS_CONFIG_FILE));
379 List<CdmServerInfo> serverInfoList = new ArrayList<CdmServerInfo>(configList.size());
380 for(CdmServerInfoConfig config : configList) {
381 serverInfoList.add(new CdmServerInfo(config));
382 }
383 // The local host managed server must not be in the config file
384 CdmServerInfoConfig localHostManagedConfig = new CdmServerInfoConfig(NAME_LOCALHOST_MGD, SERVER_LOCALHOST, NULL_PORT, CDMSERVER_PREFIX, false);
385 serverInfoList.add(new CdmServerInfo(localHostManagedConfig));
386 return serverInfoList;
387 }
388
389
390 /**
391 * @param file
392 * @throws IOException
393 * @throws FileNotFoundException
394 * @throws JsonMappingException
395 * @throws JsonParseException
396 */
397 private static List<CdmServerInfoConfig> loadFromConfigFile(File file) {
398
399 List<CdmServerInfoConfig> serverList = null;
400
401 ObjectMapper mapper = new ObjectMapper();
402
403 if(!file.exists()) {
404 logger.info("The remote server config file '" + file.getAbsolutePath() +
405 "' does not yet exist, it will be created based on the defaults.");
406 try {
407 serverList = createDefaultServerConfigList();
408 mapper.writerWithDefaultPrettyPrinter().writeValue(new FileOutputStream(file), serverList);
409
410 } catch (IOException e) {
411 throw new RuntimeException(e);
412 }
413 } else {
414 try {
415 serverList = mapper.readValue(new FileInputStream(file), new TypeReference<List<CdmServerInfoConfig>>(){});
416 } catch (IOException e) {
417 throw new RuntimeException(e);
418 }
419 }
420
421 return serverList;
422
423
424 }
425
426
427 /**
428 *
429 */
430 private static List<CdmServerInfoConfig> createDefaultServerConfigList() {
431
432 List<CdmServerInfoConfig> serverInfoList = new ArrayList<CdmServerInfoConfig>();
433 serverInfoList.add(new CdmServerInfoConfig(NAME_PRODUCTION, SERVER_PRODUCTION, 80, "", false));
434 serverInfoList.add(new CdmServerInfoConfig(NAME_DEMO_1, SERVER_DEMO_1, 80, CDMSERVER_PREFIX, false));
435 serverInfoList.add(new CdmServerInfoConfig(NAME_LOCALHOST, SERVER_LOCALHOST, 8080, CDMSERVER_PREFIX, true));
436 return serverInfoList;
437 }
438
439 public String getName() {
440 return name;
441 }
442
443 public String getServer() {
444 return server;
445 }
446
447
448 public int getPort() {
449 return port;
450 }
451
452 public void setPort(int port) {
453 this.port = port;
454 }
455
456 public List<CdmInstanceInfo> getInstances() throws CDMServerException {
457 if(instances.isEmpty()) {
458 refreshInstances();
459 }
460 return instances;
461 }
462
463 public static CdmRemoteSource getDevServerRemoteSource() {
464 String value = System.getProperty("cdm.server.dev.port");
465 boolean available = false;
466 CdmInstanceInfo devInstance = null;
467 if(value != null && !value.isEmpty()) {
468 int devPort = Integer.valueOf(value);
469 CdmServerInfo devCii = new CdmServerInfo(new CdmServerInfoConfig(NAME_LOCALHOST_DEV, SERVER_LOCALHOST_DEV, devPort, "", false));
470 try {
471 devInstance = devCii.addInstance(NAME_INSTANCE_LOCALHOST_DEV, BASEPATH_LOCALHOST_DEV);
472 available = devCii.pingInstance(devInstance, devPort);
473 if(available) {
474 return devCii.getCdmRemoteSource(devInstance, devPort);
475 }
476 } catch (Exception e) {
477
478 }
479 }
480 return null;
481 }
482
483 public class CdmInstanceInfo {
484 private final String name;
485
486 /**
487 * The full path of the instance including the the prefix (if any).
488 * E.g. for an EDIT instance this would be something like "cdmserver/remoting"
489 * For a managed local server this would simply be ""
490 */
491 private final String basePath;
492
493
494 public CdmInstanceInfo(String name, String basePath) {
495 this.name = name;
496 this.basePath = basePath;
497 }
498
499
500 public String getName() {
501 return name;
502 }
503
504 public String getBasePath() {
505 return basePath;
506 }
507 }
508 }