blob: 756ea6a6b91d727a664d0f626a4f1da5ad00ec97 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2019 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.connector.boundary;
import java.io.Serializable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.security.auth.spi.LoginModule;
import org.apache.commons.text.StringSubstitutor;
import org.apache.commons.text.lookup.StringLookup;
import org.apache.commons.text.lookup.StringLookupFactory;
import org.eclipse.mdm.api.base.ConnectionException;
import org.eclipse.mdm.api.base.ServiceNotProvidedException;
import org.eclipse.mdm.api.base.model.Environment;
import org.eclipse.mdm.api.base.query.DataAccessException;
import org.eclipse.mdm.api.dflt.ApplicationContext;
import org.eclipse.mdm.api.dflt.ApplicationContextFactory;
import org.eclipse.mdm.api.dflt.EntityManager;
import org.eclipse.mdm.connector.control.ServiceConfigurationActivity;
import org.eclipse.mdm.connector.entity.ServiceConfiguration;
import org.eclipse.mdm.property.GlobalProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* ConnectorServcie Bean implementation to create and close connections
*
* @author Sebastian Dirsch, Gigatronik Ingolstadt GmbH
* @author Canoo Engineering (removal of hardcoded ODS dependencies)
*
*/
@SessionScoped
public class ConnectorService implements Serializable {
private static final long serialVersionUID = -5891142709182298531L;
private static final Logger LOG = LoggerFactory.getLogger(ConnectorService.class);
private static final String CONNECTION_PARAM_FOR_USER = "for_user";
@Inject
Principal principal;
@Inject
ServiceConfigurationActivity serviceConfigurationActivity;
@Inject
@GlobalProperty
private Map<String, String> globalProperties = Collections.emptyMap();
private List<ApplicationContext> contexts = Lists.newArrayList();
private Map<ApplicationContext, ServiceConfiguration> serviceConfigs = new ConcurrentHashMap<>();
private transient StringLookup stringLookup = StringLookupFactory.INSTANCE.interpolatorStringLookup();
private transient StringSubstitutor substitutor = new StringSubstitutor(stringLookup);
public ConnectorService() {
// empty constructor for CDI
}
/**
* Creates a connector service for usage outside the session scope of CDI.
*
* @param principal Principal the connector service uses.
* @param globalProperties global properties supplied the opened application
* contexts.
*/
public ConnectorService(Principal principal, Map<String, String> globalProperties) {
super();
this.principal = principal;
this.serviceConfigurationActivity = new ServiceConfigurationActivity();
this.globalProperties = globalProperties;
}
/**
* returns all available {@link ApplicationContext}s
*
* @return list of available {@link ApplicationContext}s
*/
public List<ApplicationContext> getContexts() {
return ImmutableList.copyOf(contexts);
}
/**
* returns an {@link ApplicationContext} identified by the given name
*
* @param name source name (e.g. MDM {@link Environment} name)
* @return the matching {@link ApplicationContext}
*/
public ApplicationContext getContextByName(String name) {
try {
for (ApplicationContext context : getContexts()) {
String sourceName = context.getEntityManager()
.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class)).loadEnvironment()
.getSourceName();
if (sourceName.equals(name)) {
return context;
}
}
String errorMessage = "no data source with environment name '" + name + "' connected!";
throw new ConnectorServiceException(errorMessage);
} catch (DataAccessException e) {
throw new ConnectorServiceException(e.getMessage(), e);
}
}
@PostConstruct
public void connect() {
LOG.info("connecting user with name '" + principal.getName() + "'");
this.contexts = serviceConfigurationActivity.readServiceConfigurations().stream().map(this::connectContexts)
.filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
}
/**
* disconnect from all connected data sources This method is call from a
* {@link LoginModule} at logout
*
* This method is call from a {@link LoginModule}
*
*/
@PreDestroy
public void disconnect() {
for (ApplicationContext context : new ArrayList<>(contexts)) {
disconnectContext(context);
}
LOG.info("user with name '" + principal.getName() + "' has been disconnected!");
}
/**
* Tries to close the given context and reconnect.
*
* @param context ApplicationContext to reconnect
* @return the reconnected context
*/
public Optional<ApplicationContext> reconnect(ApplicationContext context) {
try {
context.close();
} catch (Exception e) {
LOG.warn("Unable to close ApplicationContext!", e);
}
ServiceConfiguration serviceConfig = serviceConfigs.remove(context);
contexts.remove(context);
if (serviceConfig == null) {
LOG.warn("Could not reconnect ApplicationContext, because service configuration is null!");
return Optional.empty();
} else {
return connectContexts(serviceConfig);
}
}
private Optional<ApplicationContext> connectContexts(ServiceConfiguration source) {
try {
Class<? extends ApplicationContextFactory> contextFactoryClass = Thread.currentThread()
.getContextClassLoader().loadClass(source.getContextFactoryClass())
.asSubclass(ApplicationContextFactory.class);
ApplicationContextFactory contextFactory = contextFactoryClass.newInstance();
Map<String, String> connectionParameters = new HashMap<>();
connectionParameters.putAll(globalProperties);
connectionParameters.putAll(processLookups(source.getConnectionParameters()));
connectionParameters.put(CONNECTION_PARAM_FOR_USER, principal.getName());
ApplicationContext context = contextFactory.connect(connectionParameters);
serviceConfigs.put(context, source);
contexts.add(context);
return Optional.of(context);
} catch (ConnectionException e) {
LOG.warn("unable to logon user with name '" + principal.getName() + "' at data source '"
+ source.getContextFactoryClass() + "' (reason: " + e.getMessage() + ")");
} catch (Exception e) {
LOG.error("failed to initialize entity manager using factory '" + source.getContextFactoryClass()
+ "' (reason: " + e + ")", e);
}
return Optional.empty();
}
/**
* Processes lookups on the values of the map
*
* @param map
* @return map with applied lookups
*/
protected Map<String, String> processLookups(Map<String, String> map) {
return map.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> substitutor.replace(e.getValue())));
}
private void disconnectContext(ApplicationContext context) {
try {
if (context != null) {
context.close();
}
} catch (ConnectionException e) {
LOG.error("unable to logout user from MDM datasource (reason: " + e.getMessage() + ")");
throw new ConnectorServiceException(e.getMessage(), e);
} finally {
serviceConfigs.remove(context);
contexts.remove(context);
}
}
}