2 * Copyright (C) 2015 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.taxeditor
.remoting
.source
;
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
;
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
;
37 import com
.fasterxml
.jackson
.core
.type
.TypeReference
;
38 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
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
;
56 public class CdmServerInfo
{
58 public static final Logger logger
= LogManager
.getLogger(CdmServerInfo
.class);
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";
64 private final static String NAME_DEMO_1
= "demo I";
65 private final static String SERVER_DEMO_1
= "160.45.63.230";
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.";
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
= "";
76 public final static int NULL_PORT
= -1;
77 public final static String NULL_PORT_STRING
= "N/A";
79 protected static final String CDM_REMOTE_SERVERS_CONFIG_FILE
= "cdm_remote_servers.json";
82 private final String name
;
83 private final String server
;
85 protected final List
<CdmInstanceInfo
> instances
;
87 private String cdmlibServicesVersion
= "";
88 private String cdmlibServicesLastModified
= "";
90 private String prefix
= "";
92 private boolean ignoreCdmLibVersion
= false;
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
<>();
104 public CdmInstanceInfo
addInstance(String name
, ICdmDataSource dataSource
) {
105 CdmInstanceInfo cii
= new CdmInstanceInfo(name
, dataSource
);
110 public CdmInstanceInfo
addInstance(String name
, String basePath
) {
111 CdmInstanceInfo cii
= new CdmInstanceInfo(name
, basePath
);
116 public boolean isLocalhost() {
117 return name
.startsWith(SERVER_LOCALHOST
);
120 public boolean isLocalhostMgd() {
121 return NAME_LOCALHOST_MGD
.equals(name
);
124 public String
getCdmlibServicesVersion() {
125 return cdmlibServicesVersion
;
128 public String
getCdmlibLastModified() {
129 return cdmlibServicesLastModified
;
132 public void refreshInstances() throws CdmServerException
{
134 if(isLocalhostMgd()) {
135 addInstancesFromDataSourcesConfig();
137 addInstancesViaHttp();
141 Collections
.sort(instances
, new Comparator
<CdmInstanceInfo
>() {
143 public int compare(CdmInstanceInfo cii1
, CdmInstanceInfo cii2
){
144 return cii1
.getName().toString().compareTo(cii2
.getName().toString());
149 public void updateInfo() throws CdmServerException
{
151 String url
= guessProtocol() + "://" + server
+ ":" + String
.valueOf(port
) + "/" + prefix
+ "info.jsp";
152 String responseBody
= getResponse(url
);
153 if(responseBody
!= null) {
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
);
164 String
guessProtocol() {
165 return port
== 443 ?
"https" : "http";
168 public void addInstancesViaHttp() throws CdmServerException
{
170 String url
= guessProtocol() + "://" + server
+ ":" + String
.valueOf(port
) + "/" + prefix
+ "instances.jsp";
171 String responseBody
= getResponse(url
);
172 if(responseBody
!= null) {
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");
180 String instanceName
= conf
.getString("instanceName");
181 // we need to remove the first (char) forward slash from
183 String basePath
= conf
.getString("basePath").substring(1);
184 addInstance(instanceName
, basePath
);
185 logger
.info("Added instance with name : " + instanceName
+ ", basePath : " + basePath
);
189 } catch (JSONException e
) {
190 throw new CdmServerException(e
);
195 private String
getResponse(String url
) throws CdmServerException
{
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();
203 HttpGet httpGet
= new HttpGet(url
);
205 logger
.info("Executing request " + httpGet
.getRequestLine());
207 // Create a custom response handler
208 ResponseHandler
<String
> responseHandler
= new ResponseHandler
<String
>() {
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;
218 throw new ClientProtocolException("Unexpected response status: " + status
);
223 String responseBody
= null;
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
);
234 } catch (IOException e
) {
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
);
249 public String
toString(String instanceName
, int port
) {
250 return server
+ ":" + String
.valueOf(port
) + "/" + instanceName
;
253 public CdmInstanceInfo
getInstanceFromName(String instanceName
) {
254 if(instanceName
== null) {
258 for(CdmInstanceInfo instance
: instances
) {
259 if(instance
.getName() != null && instance
.getName().equals(instanceName
)) {
266 public ICdmRemoteSource
getCdmRemoteSource(CdmInstanceInfo instance
, int port
) {
267 if(instance
!= null) {
268 if (instance
.getDataSource()!=null){
269 return CdmRemoteLocalhostSource
.NewInstance(name
,
272 instance
.getDataSource(),
276 return CdmRemoteSource
.NewInstance(name
,
279 instance
.getBasePath()
286 public boolean pingServer() throws CdmServerException
, IOException
{
287 if(isLocalhostMgd()) {
290 Socket s
= new Socket();
291 s
.connect(new InetSocketAddress(server
, port
), CdmApplicationRemoteConfiguration
.HTTP_READ_TIMEOUT_MIN
);
293 logger
.info("[CDM-Server] Available @ " + server
+ ":" + port
);
298 public boolean pingInstance(CdmInstanceInfo instance
, int port
) throws CdmServerException
{
299 ICdmSource cdmRemoteSource
= getCdmRemoteSource(instance
, port
);
301 if(cdmRemoteSource
!= null && cdmRemoteSource
.checkConnection()) {
302 logger
.info("[CDM-Server] Running @ " + server
+ ":" + port
+ " for instance " + instance
);
305 } catch (CdmSourceException e
) {
306 logger
.error(e
.getMessage(), e
);
307 throw new CdmServerException(e
);
313 public int compareDbSchemaVersion(CdmInstanceInfo instance
, int port
) throws CdmServerException
{
315 ICdmSource remoteSource
= getCdmRemoteSource(instance
, port
);
316 String remoteDbSchemaVersion
;
318 remoteDbSchemaVersion
= remoteSource
.getDbSchemaVersion();
319 } catch (CdmSourceException e
) {
320 throw new CdmServerException(e
);
323 if(remoteDbSchemaVersion
!= null) {
324 return CdmMetaData
.compareVersion(remoteDbSchemaVersion
, CdmMetaData
.getDbSchemaVersion(), 3, null);
326 throw new CdmServerException("Cannot determine editor db schema version");
330 public int compareCdmlibServicesVersion() throws CdmSourceException
{
332 String serverVersion
= cdmlibServicesVersion
;
333 String serverCdmlibLastModified
= cdmlibServicesLastModified
;
334 if(ignoreCdmLibVersion
) {
337 return compareCdmlibServicesVersion(serverVersion
, serverCdmlibLastModified
);
341 public static int compareCdmlibServicesVersion(String serverVersion
, String serverCdmlibLastModified
) throws CdmSourceException
{
343 String editorVersion
= CdmApplicationState
.getCdmlibVersion();
344 String editorCdmlibLastModified
= CdmApplicationState
.getCdmlibLastModified();
347 if(StringUtils
.isBlank(serverVersion
) || StringUtils
.isBlank(editorVersion
)) {
348 throw new CdmSourceException("cdmlib-services server or editor version is empty");
351 String
[] serverVersionSplit
= serverVersion
.split("\\.");
352 String
[] editorVersionSplit
= editorVersion
.split("\\.");
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");
358 Integer serverVersionPart
;
359 Integer editorVersionPart
;
361 for(int i
=0 ; i
<3 ; i
++) {
362 serverVersionPart
= Integer
.valueOf(serverVersionSplit
[i
]);
363 editorVersionPart
= Integer
.valueOf(editorVersionSplit
[i
]);
365 int partCompare
= serverVersionPart
.compareTo(editorVersionPart
);
366 if (partCompare
!= 0){
370 // at this point major, minor and patch versions are matching
372 if(StringUtils
.isBlank(serverCdmlibLastModified
) || StringUtils
.isBlank(editorCdmlibLastModified
)) {
373 throw new CdmSourceException("cdmlib-services server or editor version is empty");
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
);
386 public static List
<CdmServerInfo
> getCdmServers(boolean isLocal
) {
387 List
<CdmServerInfoConfig
> configList
;
388 File file
= new File(ConfigFileUtil
.perUserCdmFolderFallback(), CDM_REMOTE_SERVERS_CONFIG_FILE
);
390 configList
= loadFromConfigFile(file
);
392 configList
= loadFromConfigFile(new File(ConfigFileUtil
.perUserCdmFolderFallback(), CDM_REMOTE_SERVERS_CONFIG_FILE
));
394 List
<CdmServerInfo
> serverInfoList
= new ArrayList
<>(configList
.size());
395 for(CdmServerInfoConfig config
: configList
) {
396 serverInfoList
.add(new CdmServerInfo(config
));
398 // The local host managed server must not be in the config file, this should be moved and only added when plugin is installed
400 CdmServerInfoConfig localHostManagedConfig
= new CdmServerInfoConfig(NAME_LOCALHOST_MGD
, SERVER_LOCALHOST
, NULL_PORT
, CDMSERVER_PREFIX
, false);
401 serverInfoList
.add(new CdmServerInfo(localHostManagedConfig
));
404 return serverInfoList
;
407 protected static List
<CdmServerInfoConfig
> loadFromConfigFile(File file
) {
409 List
<CdmServerInfoConfig
> serverList
= null;
411 ObjectMapper mapper
= new ObjectMapper();
414 logger
.info("The remote server config file '" + file
.getAbsolutePath() +
415 "' does not yet exist, it will be created based on the defaults.");
417 serverList
= createDefaultServerConfigList();
418 mapper
.writerWithDefaultPrettyPrinter().writeValue(new FileOutputStream(file
), serverList
);
420 } catch (IOException e
) {
421 throw new RuntimeException(e
);
425 serverList
= mapper
.readValue(new FileInputStream(file
), new TypeReference
<List
<CdmServerInfoConfig
>>(){});
426 } catch (IOException e
) {
427 throw new RuntimeException(e
);
434 private static List
<CdmServerInfoConfig
> createDefaultServerConfigList() {
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
;
443 public String
getName() {
447 public String
getServer() {
451 public int getPort() {
455 public void setPort(int port
) {
459 public List
<CdmInstanceInfo
> getInstances() throws CdmServerException
{
460 if(instances
.isEmpty()) {
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));
474 devInstance
= devCii
.addInstance(NAME_INSTANCE_LOCALHOST_DEV
, BASEPATH_LOCALHOST_DEV
);
475 available
= devCii
.pingInstance(devInstance
, devPort
);
477 logger
.info("Will connect local development cdm instance: at port " + devPort
);
478 return devCii
.getCdmRemoteSource(devInstance
, devPort
);
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
487 logger
.error("local development cdm instance at port " + devPort
+ " is not accessible");
492 public class CdmInstanceInfo
{
494 private final String name
;
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 ""
501 private final String basePath
;
503 private final ICdmDataSource dataSource
;
505 public CdmInstanceInfo(String name
, String basePath
) {
507 this.basePath
= basePath
;
508 this.dataSource
= null;
511 public CdmInstanceInfo(String name
, ICdmDataSource dataSource
) {
514 this.dataSource
= dataSource
;
517 public String
getName() {
521 public String
getBasePath() {
525 public ICdmDataSource
getDataSource() {