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