Commit c69ba15b authored by Marco Descher's avatar Marco Descher 🏔

[16515] Refactor IMessageService and dependents

parent cf3e8f45
Pipeline #11456 failed with stages
in 1 minute and 34 seconds
package ch.elexis.core.services;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.osgi.service.component.annotations.Component;
import ch.elexis.core.model.IMessage;
......@@ -18,25 +19,29 @@ public class InternalDatabaseMessageTransporter implements IMessageTransporter {
public IStatus send(TransientMessage message){
IMessage idbMessage = CoreModelServiceHolder.get().create(IMessage.class);
// TODO copy all
idbMessage.setSender(message.getSender());
idbMessage.setMessageText(message.getMessageText());
idbMessage.setMessageCodes(message.getMessageCodes());
idbMessage.setMessagePriority(message.getMessagePriority());
idbMessage.setCreateDateTime(message.getCreateDateTime());
idbMessage.setSenderAcceptsAnswer(message.isSenderAcceptsAnswer());
// IMessage persistedMessage = CoreModelServiceHolder.get().create(IMessage.class);
boolean save = CoreModelServiceHolder.get().save(idbMessage);
if (save) {
return Status.OK_STATUS;
}
// boolean result = CoreModelServiceHolder.get().save(message);
// if(result) {
// return Status.OK_STATUS;
// }
return ObjectStatus.ERROR_STATUS(message.getId());
return ObjectStatus.ERROR_STATUS(idbMessage.getId());
}
@Override
public int getDefaultPriority(){
return 0;
}
@Override
public String getId(){
return "internaldatabase";
public String getUriScheme(){
return "internaldb";
}
@Override
public boolean isExternal(){
return false;
}
}
package ch.elexis.core.services;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.LoggerFactory;
import ch.elexis.core.model.IUser;
import ch.elexis.core.model.message.TransientMessage;
import ch.elexis.core.services.holder.CoreModelServiceHolder;
import ch.elexis.core.status.StatusUtil;
import ch.elexis.core.services.internal.Bundle;
import ch.elexis.core.status.ObjectStatus;
@Component
public class MessageService implements IMessageService {
@Reference
private IContextService contextService;
/**
* all transporters available
*/
private List<IMessageTransporter> messageTransporters;
/**
* transporters considered for default message transportation
*/
private List<IMessageTransporter> defaultTransporters;
private Map<String, IMessageTransporter> messageTransporters;
public MessageService(){
messageTransporters = new ArrayList<>();
defaultTransporters = new ArrayList<>();
messageTransporters = new HashMap<>();
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
public void setMessageTransporter(IMessageTransporter messageTransporter){
if (!messageTransporters.contains(messageTransporter)) {
messageTransporters.add(messageTransporter);
if (messageTransporter.getDefaultPriority() >= 0) {
defaultTransporters.add(messageTransporter);
}
Collections.sort(defaultTransporters);
if (!messageTransporters.containsKey(messageTransporter.getUriScheme())) {
messageTransporters.put(messageTransporter.getUriScheme(), messageTransporter);
}
}
public void unsetMessageTransporter(IMessageTransporter messageTransporter){
if (messageTransporters.contains(messageTransporter)) {
messageTransporters.remove(messageTransporter);
if (messageTransporter.getDefaultPriority() >= 0) {
defaultTransporters.remove(messageTransporter);
}
Collections.sort(defaultTransporters);
if (messageTransporters.containsKey(messageTransporter.getUriScheme())) {
messageTransporters.remove(messageTransporter.getUriScheme());
}
}
@Override
public TransientMessage prepare(String sender, String... receiver){
boolean senderIsUser = CoreModelServiceHolder.get().load(sender, IUser.class).isPresent();
return new TransientMessage(sender, senderIsUser, receiver);
}
@Override
public TransientMessage prepare(IUser sender, String... receiver){
return new TransientMessage(sender.getId(), true, receiver);
public TransientMessage prepare(String sender, String receiver){
return new TransientMessage(sender, receiver);
}
@Override
public List<IMessageTransporter> getAvailableTransporters(){
return new ArrayList<IMessageTransporter>(messageTransporters);
public List<String> getSupportedUriSchemes(){
return new ArrayList<String>(messageTransporters.keySet());
}
@Override
public IStatus send(TransientMessage message){
List<IMessageTransporter> consideredTransporters;
List<String> preferredTransporters = message.getPreferredTransporters();
if (preferredTransporters.isEmpty()) {
consideredTransporters = defaultTransporters;
} else {
consideredTransporters = new ArrayList<>();
// TODO populate
public ObjectStatus send(TransientMessage message){
String receiver = message.getReceiver();
int indexOf = receiver.indexOf(':');
if (indexOf <= 0) {
return new ObjectStatus(Status.ERROR, Bundle.ID,
"No transporter uri scheme found in receiver [" + receiver + "]", null);
}
IStatus status = null;
for (IMessageTransporter messageTransporter : consideredTransporters) {
status = messageTransporter.send(message);
if (status.isOK()) {
return status;
} else {
StatusUtil.logStatus(LoggerFactory.getLogger(getClass()), status);
}
// if CANCEL or ERROR continue
String uriScheme = receiver.substring(0, indexOf);
IMessageTransporter messageTransporter = null;
if (uriScheme.equals(INTERNAL_MESSAGE_URI_SCHEME)) {
messageTransporter = selectInternalSchemeTransporter();
} else {
messageTransporter = messageTransporters.get(uriScheme);
}
if (status == null) {
status = new Status(Status.ERROR, "", "No message transporter found");
if (messageTransporter == null) {
return new ObjectStatus(Status.ERROR, Bundle.ID,
"No transporter found for uri scheme found [" + uriScheme + "]", null);
}
return status;
return new ObjectStatus(messageTransporter.send(message),
messageTransporter.getUriScheme());
}
/**
* Select a transporter for an internal message. Currently we prefer the rocketchat transporter
* (if available).
*
* @return the transporter or <code>null</code> if none available
*/
private IMessageTransporter selectInternalSchemeTransporter(){
IMessageTransporter messageTransporter = messageTransporters.get("rocketchat");
if (messageTransporter == null) {
messageTransporter = messageTransporters.get("internaldb");
}
return messageTransporter;
}
}
......@@ -7,7 +7,9 @@ import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
......@@ -19,6 +21,7 @@ import ch.elexis.core.eenv.IElexisEnvironmentService;
import ch.elexis.core.model.message.TransientMessage;
import ch.elexis.core.services.IContextService;
import ch.elexis.core.services.IMessageTransporter;
import ch.elexis.core.services.internal.Bundle;
@Component
public class RocketchatMessageTransporter implements IMessageTransporter {
......@@ -36,30 +39,20 @@ public class RocketchatMessageTransporter implements IMessageTransporter {
private IContextService contextService;
@Override
public int getDefaultPriority(){
return 10;
public String getUriScheme(){
return "rocketchat";
}
@Override
public String getId(){
return "rocketchat";
public boolean isExternal(){
return false;
}
@Override
public IStatus send(TransientMessage message){
if (message.isSenderIsUser()) {
return sendFromUserSender(message);
}
return sendFromStationSender(message);
}
private IStatus sendFromUserSender(TransientMessage message){
// TODO where to get integrationtoken from? what if we are user - not elexis-server?
// is the user logged in to rocketchat?
return new Status(Status.ERROR, "", "Not yet implemented");
}
private IStatus sendFromStationSender(TransientMessage message){
Optional<String> authorizationToken = contextService
.getNamed(CTX_ROCKETCHAT_STATION_INTEGRATION_TOKEN).map(e -> e.toString());
......@@ -73,24 +66,30 @@ public class RocketchatMessageTransporter implements IMessageTransporter {
return send(integrationUrl, jsonMessage.getBytes());
} catch (IOException e) {
e.printStackTrace();
return new Status(Status.ERROR, Bundle.ID, e.getMessage());
}
}
return new Status(Status.ERROR, "",
return new Status(Status.ERROR, Bundle.ID,
"No webhook integration token [" + CTX_ROCKETCHAT_STATION_INTEGRATION_TOKEN
+ "] found in root context or malformed url.");
}
private String prepareRocketchatMessage(TransientMessage message){
JSONObject json = new JSONObject();
json.put("username", contextService.getStationIdentifier());
json.put("username", message.getSender());
StringBuilder header = new StringBuilder();
message.getReceiver().forEach(c -> header.append("@" + c + " "));
header.append("|");
message.getMessageCodes().entrySet()
.forEach(c -> header.append(c.getKey() + ":" + c.getValue() + " "));
header
.append("@" + message.getReceiver().substring(message.getReceiver().indexOf(':') + 1));
Set<Entry<String, String>> entrySet = message.getMessageCodes().entrySet();
if (!entrySet.isEmpty()) {
header.append(" | ");
message.getMessageCodes().entrySet()
.forEach(c -> header.append(c.getKey() + ":" + c.getValue() + " "));
}
json.put("text", header.toString());
Map<String, Object> params = new HashMap<>();
......@@ -116,7 +115,8 @@ public class RocketchatMessageTransporter implements IMessageTransporter {
if (responseCode == 200) {
return Status.OK_STATUS;
}
return new Status(Status.ERROR, "", "Error sending, with response code: " + responseCode);
return new Status(Status.ERROR, Bundle.ID,
"Error sending, with response code: " + responseCode);
}
}
package ch.elexis.core.services.internal;
public class Bundle {
public static final String ID = "ch.elexis.core.services";
}
......@@ -25,7 +25,6 @@ import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.elexis.core.model.IMessage;
import ch.elexis.core.model.IUser;
import ch.elexis.core.model.message.MessageCode;
import ch.elexis.core.model.message.TransientMessage;
......@@ -275,8 +274,9 @@ public class TaskServiceImpl implements ITaskService {
}
private void sendMessageToOwner(ITask task, IUser owner, TaskState state){
TransientMessage message = messageService
.prepare(contextService.getRootContext().getStationIdentifier(), owner.getId());
TransientMessage message = messageService.prepare(
"Task-Service@" + contextService.getRootContext().getStationIdentifier(),
IMessageService.INTERNAL_MESSAGE_URI_SCHEME + owner.getId());
message.addMessageCode(MessageCode.Key.SenderSubId, "tasks.taskservice");
message.setSenderAcceptsAnswer(false);
......
package ch.elexis.core.model.message;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import ch.elexis.core.model.IUser;
public class TransientMessage {
private final String id;
private final String sender;
private final boolean senderIsUser;
private List<String> receiver = new ArrayList<>();
private final String receiver;
private boolean senderAcceptsAnswer;
private LocalDateTime createDateTime;
private String messageText;
private Map<String, String> messageCodes = new HashMap<>();
private int priority;
private List<String> preferredTransporters = new ArrayList<>();
/**
* Allow this message to leave the internal system, e.g. SMS or mail message
*/
private boolean alllowExternal = false;
public TransientMessage(String sender, boolean senderIsUser, String[] receiver){
public TransientMessage(String sender, String receiver){
this.sender = sender;
this.senderIsUser = senderIsUser;
this.receiver.addAll(Arrays.asList(receiver));
this.id = UUID.randomUUID().toString();
this.receiver = receiver;
senderAcceptsAnswer = true;
createDateTime = LocalDateTime.now();
priority = 0;
}
public String getId(){
return id;
}
public String getSender(){
return sender;
}
public boolean isSenderIsUser(){
return senderIsUser;
}
public List<String> getReceiver(){
public String getReceiver(){
return receiver;
}
......@@ -97,16 +84,12 @@ public class TransientMessage {
messageCodes.put(key, value);
}
public void addReceiver(String receiver){
this.receiver.add(receiver);
}
public void addReceiver(IUser receiver){
this.receiver.add(receiver.getId());
public boolean isAlllowExternal(){
return alllowExternal;
}
public List<String> getPreferredTransporters(){
return preferredTransporters;
public void setAlllowExternal(boolean alllowExternal){
this.alllowExternal = alllowExternal;
}
}
\ No newline at end of file
......@@ -2,46 +2,44 @@ package ch.elexis.core.services;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import ch.elexis.core.model.IMessage;
import ch.elexis.core.model.IUser;
import ch.elexis.core.model.message.TransientMessage;
import ch.elexis.core.status.ObjectStatus;
/**
* Handles transportation of an {@link IMessage}
* Transport a message from a sender to a receiver
*/
public interface IMessageService {
/**
* @return the available message transporters
*/
List<IMessageTransporter> getAvailableTransporters();
public static final String INTERNAL_MESSAGE_URI_SCHEME = "internal";
/**
* Create an {@link IMessage} object for further parameterization
*
* @param sender
* @param receiver
* @return
* @return a list of all supported uri schemes
*/
TransientMessage prepare(String sender, String... receiver);
List<String> getSupportedUriSchemes();
/**
* Convenience method for {@link #prepare(String, String...)}
*
* @param sender
* @param receiver
* a string which may or may be not resolvable to a user or supported scheme
* @param recipientUri
* a recipient URI scheme and the user.<br>
* For internal communication either explicitly select the transporter e.g.
* <code>internaldb:user</code>, <code>rocketchat:user</code> or use the implicit
* {@link #INTERNAL_MESSAGE_URI_SCHEME} to leave the choice to the system.<br>
* For external communication use <code>mailto:user@bla.com</code> or
* <code>sms:+4133423</code> (if available). A message has to be explicitly marked
* with {@link TransientMessage#setAlllowExternal(boolean)} to use an external
* transporter.
* @return
*/
TransientMessage prepare(IUser sender, String... receiver);
TransientMessage prepare(String sender, String recipientUri);
/**
* Try to send the message.
*
* @param message
* @return
* @return if the message was sent successfully and the explicit transporter uri scheme used
*/
IStatus send(TransientMessage message);
ObjectStatus send(TransientMessage message);
}
......@@ -5,19 +5,12 @@ import org.eclipse.core.runtime.IStatus;
import ch.elexis.core.model.IMessage;
import ch.elexis.core.model.message.TransientMessage;
public interface IMessageTransporter extends Comparable<IMessageTransporter> {
public interface IMessageTransporter {
/**
* @return the default priority of this transporter, higher value is higher priority. If the
* priority is lower than 0, the transporter is not added as a default transporter, and
* may only be addressed via {@link IMessage#getPreferredTransporters()}
* @return the uri scheme denominator
*/
int getDefaultPriority();
/**
* @return a unique identifier
*/
String getId();
String getUriScheme();
/**
* Try to send an {@link IMessage}.
......@@ -30,8 +23,11 @@ public interface IMessageTransporter extends Comparable<IMessageTransporter> {
*/
IStatus send(TransientMessage message);
@Override
default int compareTo(IMessageTransporter other){
return Integer.compare(getDefaultPriority(), other.getDefaultPriority());
}
/**
*
* @return whether a message transported with this transporter "leaves the site boundary". E.g.
* an SMS transporter
*/
boolean isExternal();
}
package ch.elexis.core.services;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public abstract class AbstractServiceTest {
static IModelService coreModelService = AllServiceTests.getModelService();
/**
* Accept all HTTPS certificates (used for servers running a self-signed certificate)
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
public static void acceptAllCerts() throws NoSuchAlgorithmException, KeyManagementException{
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers(){
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType){}
public void checkServerTrusted(X509Certificate[] certs, String authType){}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session){
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
}
}
......@@ -15,42 +15,45 @@ import ch.elexis.core.test.initializer.TestDatabaseInitializer;
import ch.elexis.core.utils.OsgiServiceUtil;
@RunWith(Suite.class)
@SuiteClasses({ AccessControlServiceTest.class, IConfigServiceTest.class, ILabServiceTest.class,
IStoreToStringServiceTest.class, IUserServiceTest.class, IStickerServiceTest.class, IAppointmentServiceTest.class,
IVirtualFilesystemServiceTest.class, IXidServiceTest.class})
@SuiteClasses({
AccessControlServiceTest.class, IConfigServiceTest.class, IElexisEnvironmentServiceTest.class,
ILabServiceTest.class, IStoreToStringServiceTest.class, IStickerServiceTest.class,
IAppointmentServiceTest.class, IUserServiceTest.class, IMessageServiceTest.class,
IVirtualFilesystemServiceTest.class, IXidServiceTest.class
})
public class AllServiceTests {
private static IModelService modelService;
private static IElexisEntityManager entityManager;
private static TestDatabaseInitializer tdb;
@BeforeClass
public static void beforeClass() throws IOException, SQLException {
modelService = OsgiServiceUtil
.getService(IModelService.class, "(" + IModelService.SERVICEMODELNAME + "=ch.elexis.core.model)").get();
public static void beforeClass() throws IOException, SQLException{
modelService = OsgiServiceUtil.getService(IModelService.class,
"(" + IModelService.SERVICEMODELNAME + "=ch.elexis.core.model)").get();
entityManager = OsgiServiceUtil.getService(IElexisEntityManager.class).get();
tdb = new TestDatabaseInitializer(modelService, entityManager);
tdb.initializePatient();
tdb.initializeLabResult();
tdb.initializePrescription();
}
public static IModelService getModelService() {
public static IModelService getModelService(){
return modelService;
}
public static IPatient getPatient() {
public static IPatient getPatient(){
return tdb.getPatient();
}
public static ILaboratory getLaboratory() {
public static ILaboratory getLaboratory(){
return tdb.getLaboratory();
}
public static IArticle getEigenartikel() {
public static IArticle getEigenartikel(){
return tdb.getArticle();
}
}
package ch.elexis.core.services;
import static org.junit.Assert.assertTrue;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Optional;
import org.eclipse.core.runtime.IStatus;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import ch.elexis.core.eenv.IElexisEnvironmentService;
import ch.elexis.core.model.message.MessageCode;
import ch.elexis.core.model.message.TransientMessage;
import ch.elexis.core.utils.OsgiServiceUtil;
public class IElexisEnvironmentServiceTest extends AbstractServiceTest {
private Optional<IElexisEnvironmentService> ee_service =
OsgiServiceUtil.getService(IElexisEnvironmentService.class);
private IMessageService messageService =
OsgiServiceUtil.getService(IMessageService.class).get();
private static IContextService contextService =
OsgiServiceUtil.getService(IContextService.class).get();
@BeforeClass
public static void beforeClass() throws NoSuchAlgorithmException, KeyManagementException{
// set it per station
contextService.getRootContext().setNamed("rocketchat-station-integration-token",
"b8fnKyMcMTRyeg22d/hM3LTStZheEt7w3L6fu8rDDNqcJiXWbbvmKsRrP2zm8zTYoA");
acceptAllCerts();
}
@Test
public void rocketChatMessage_fromStation(){
Assume.assumeTrue(ee_service.isPresent());