
org.ow2.petals.bc.gateway.outbound.ProviderDomain Maven / Gradle / Ivy
/**
* Copyright (c) 2015-2016 Linagora
*
* This program/library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at your
* option) any later version.
*
* This program/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program/library; If not, see http://www.gnu.org/licenses/
* for the GNU Lesser General Public License version 2.1.
*/
package org.ow2.petals.bc.gateway.outbound;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.jbi.messaging.MessageExchange;
import javax.xml.namespace.QName;
import org.eclipse.jdt.annotation.Nullable;
import org.ow2.petals.bc.gateway.JBISender;
import org.ow2.petals.bc.gateway.commons.AbstractDomain;
import org.ow2.petals.bc.gateway.commons.messages.ServiceKey;
import org.ow2.petals.bc.gateway.commons.messages.TransportedDocument;
import org.ow2.petals.bc.gateway.commons.messages.TransportedMessage;
import org.ow2.petals.bc.gateway.commons.messages.TransportedPropagations;
import org.ow2.petals.bc.gateway.jbidescriptor.generated.JbiProviderDomain;
import org.ow2.petals.bc.gateway.jbidescriptor.generated.JbiProvidesConfig;
import org.ow2.petals.bc.gateway.utils.BcGatewayJbiHelper.Pair;
import org.ow2.petals.bc.gateway.utils.BcGatewayProvideExtFlowStepBeginLogData;
import org.ow2.petals.bc.gateway.utils.BcGatewayServiceEndpointHelper;
import org.ow2.petals.commons.log.FlowAttributes;
import org.ow2.petals.commons.log.Level;
import org.ow2.petals.commons.log.PetalsExecutionContext;
import org.ow2.petals.component.framework.api.exception.PEtALSCDKException;
import org.ow2.petals.component.framework.api.message.Exchange;
import org.ow2.petals.component.framework.jbidescriptor.generated.Provides;
import org.ow2.petals.component.framework.logger.StepLogHelper;
import org.ow2.petals.component.framework.su.ServiceUnitDataHandler;
import org.ow2.petals.component.framework.util.EndpointUtil;
import org.ow2.petals.component.framework.util.ServiceEndpointKey;
import org.w3c.dom.Document;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.serialization.ClassResolver;
/**
* There is one instance of this class per opened connection to a provider partner.
*
* It maintains the list of Provides we should create on our side (based on the Consumes propagated)
*
* {@link #connect()} and {@link #disconnect()} corresponds to components start and stop. {@link #connect()} should
* trigger {@link #updatePropagatedServices(TransportedPropagations)} by the {@link Channel} normally.
*
* {@link #register()} and {@link #deregister()} corresponds to SU init and shutdown.
*
*/
public class ProviderDomain extends AbstractDomain {
/**
* For now we only accept ONE provides per propagated service, but there is no reason to do so, we could have many
* provides for a given matching service (as long as each provides matches only one service of course!).
*
* TODO support multi provides per service (it's complicated because then we have to rething {@link #services} that
* won't match the received services anymore)
*
* TODO support multiple activation of a provides (by changing its endpoint name thus) if multiple
* {@link ServiceKey} matches.
*/
private final Service2ProvidesMatcher service2provides;
private final ProviderMatcher matcher;
private final TransportClient client;
/**
* lock for manipulating the {@link #services}, {@link #jpd} and {@link #init},
*/
private final Lock mainLock = new ReentrantLock(true);
/**
* Updated by {@link #updatePropagatedServices(TransportedPropagations)}.
*
* Contains the services announced by the provider partner as being propagated.
*
* The content of {@link ServiceData} itself is updated by {@link #register()} and {@link #deregister()} (to add the
* {@link ServiceEndpointKey} that is activated as an endpoint)
*
*/
private final Map services = new HashMap<>();
/**
* Not final because it can be updated by {@link #reload(JbiProviderDomain)}.
*/
private JbiProviderDomain jpd;
private boolean init = false;
private static class ServiceData {
private @Nullable Document description;
private @Nullable ServiceEndpointKey key;
public ServiceData(final @Nullable Document description) {
this.description = description;
}
}
public ProviderDomain(final ProviderMatcher matcher, final ServiceUnitDataHandler handler,
final JbiProviderDomain jpd, final Collection> provides,
final JBISender sender, final Bootstrap partialBootstrap, final Logger logger, final ClassResolver cr)
throws PEtALSCDKException {
super(sender, handler, logger);
this.matcher = matcher;
this.jpd = jpd;
this.service2provides = new Service2ProvidesMatcher(provides);
this.client = new TransportClient(handler, partialBootstrap, logger, cr, this);
}
@Override
public String getId() {
return jpd.getId();
}
public void reload(final JbiProviderDomain newJPD) {
mainLock.lock();
try {
if (!jpd.getRemoteAuthName().equals(newJPD.getRemoteAuthName())
|| !jpd.getRemoteIp().equals(newJPD.getRemoteIp())
|| !jpd.getRemotePort().equals(newJPD.getRemotePort())
|| !jpd.getCertificate().equals(newJPD.getCertificate())
|| !jpd.getRemoteCertificate().equals(newJPD.getRemoteCertificate())
|| !jpd.getKey().equals(newJPD.getKey()) || !jpd.getPassphrase().equals(newJPD.getPassphrase())) {
jpd = newJPD;
connect(true);
}
} finally {
mainLock.unlock();
}
}
/**
* Register propagated consumes for the JBI listener, can be called after or before the component has started (i.e.,
* {@link #connect()} has been called).
*/
public void register() throws PEtALSCDKException {
mainLock.lock();
try {
for (final Entry e : services.entrySet()) {
final ServiceKey sk = e.getKey();
final ServiceData data = e.getValue();
assert sk != null;
assert data != null;
registerProviderService(sk, data);
}
init = true;
} catch (final PEtALSCDKException e) {
logger.severe("Error during ProviderDomain init, undoing everything");
for (final ServiceData data : services.values()) {
assert data != null;
deregisterOrStoreOrLog(data, null);
}
throw e;
} finally {
mainLock.unlock();
}
}
/**
* Deregister the propagated consumes for the JBI Listener
*/
public void deregister() throws PEtALSCDKException {
final List exceptions = new ArrayList<>();
mainLock.lock();
try {
init = false;
for (final ServiceData data : services.values()) {
assert data != null;
deregisterOrStoreOrLog(data, exceptions);
}
} finally {
mainLock.unlock();
}
if (!exceptions.isEmpty()) {
final PEtALSCDKException ex = new PEtALSCDKException("Errors during ProviderDomain shutdown");
for (final Exception e : exceptions) {
ex.addSuppressed(e);
}
throw ex;
}
}
public void updatePropagatedServices(final TransportedPropagations propagatedServices) {
updatePropagatedServices(propagatedServices.getPropagations());
}
/**
*
* This registers and initialises the consumes being declared in the provider domain that we mirror on this side.
*
* We receive this notification once we are connected to the other side, i.e., just after component start (and of
* course after SU deploy)
*
* It can be executed after or before {@link #register()} has been called.
*
* In case of reconnection, it can be called again or if there is an update from the other side.
*/
private void updatePropagatedServices(final Map propagated) {
mainLock.lock();
try {
final Set oldKeys = new HashSet<>(services.keySet());
// TODO handle exceptions and log them?
for (final Entry entry : propagated.entrySet()) {
final ServiceKey service = entry.getKey();
assert service != null;
final Document document = entry.getValue() != null ? entry.getValue().getDocument() : null;
// let's skip those we are not concerned with
if (!jpd.isPropagateAll() && service2provides.getProvides(service) == null) {
continue;
}
final boolean register;
final ServiceData data;
if (oldKeys.remove(service)) {
// we already knew this service from a previous event
data = services.get(service);
assert data != null;
if (document != null && data.description == null) {
final Provides p = service2provides.getProvides(service);
if (p != null && p.getWsdl() != null) {
// in this case we deregister and re-register it with the right document
data.description = document;
deregisterOrStoreOrLog(data, null);
register = true;
} else {
// in this case, we anyway use the provides description
register = false;
}
} else {
// in this case we don't touch it
register = false;
}
} else {
// the service is new!
data = new ServiceData(document);
register = true;
}
if (register) {
try {
if (init) {
registerProviderService(service, data);
}
// we add it after we are sure no error happened with the registration
services.put(service, data);
} catch (final PEtALSCDKException e) {
logger.log(Level.WARNING,
"Couldn't register propagated service '" + service + "' (" + data.key + ")",
e);
}
}
}
// these services from a previous connection do not exist anymore!
for (final ServiceKey sk : oldKeys) {
final ServiceData data = services.remove(sk);
assert data != null;
deregisterOrStoreOrLog(data, null);
}
} finally {
mainLock.unlock();
}
}
private void registerProviderService(final ServiceKey sk, final ServiceData data) throws PEtALSCDKException {
final ProviderService provider = new ProviderService() {
@Override
public void sendToChannel(final Exchange exchange) {
ProviderDomain.this.sendToChannel(sk, exchange);
}
};
final Provides p = service2provides.getProvides(sk);
final ServiceEndpointKey key;
final QName interfaceName;
final boolean generateDescription;
if (p != null) {
key = new ServiceEndpointKey(p);
interfaceName = p.getInterfaceName();
if (p.getWsdl() == null) {
generateDescription = true;
} else {
// we will use the description managed by the ServiceUnitManager, the component will retrieve it
generateDescription = false;
}
} else {
key = generateSEK(sk);
generateDescription = true;
interfaceName = sk.interfaceName;
}
assert interfaceName != null;
data.key = key;
if (generateDescription) {
// note: data.description can be null!
final Document description = BcGatewayServiceEndpointHelper.generateDescription(data.description, sk, key,
interfaceName, logger);
matcher.register(key, provider, description);
} else {
matcher.register(key, provider);
}
}
private void deregisterOrStoreOrLog(final ServiceData data, final @Nullable Collection exceptions) {
final ServiceEndpointKey key = data.key;
if (key != null) {
try {
data.key = null;
if (!matcher.deregister(key)) {
logger.warning("Expected to deregister '" + key + "' but it wasn't registered...");
}
} catch (final PEtALSCDKException e) {
if (exceptions != null) {
exceptions.add(e);
} else {
logger.log(Level.WARNING, "Couldn't deregister propagated service '" + key + "'", e);
}
}
} else {
assert !init;
}
}
private static ServiceEndpointKey generateSEK(final ServiceKey sk) {
// Note: we should not propagate endpoint name, it is local to each domain
final String endpointName = EndpointUtil.generateEndpointName();
final ServiceEndpointKey key = new ServiceEndpointKey(sk.service, endpointName);
return key;
}
/**
* This is used to send to the channel for (1st step) exchanges arriving on JBI
*
* 3rd is taken care of by {@link AbstractDomain}.
*/
private void sendToChannel(final ServiceKey service, final Exchange exchange) {
final MessageExchange mex = exchange.getMessageExchange();
assert mex != null;
// current provide step
final FlowAttributes provideStep = PetalsExecutionContext.getFlowAttributes();
// step for the external call
final FlowAttributes provideExtStep = PetalsExecutionContext.nextFlowStepId();
// we cheat a bit and come back to the previous one for the following
// and will switch to the ext one just before sending over the channel
PetalsExecutionContext.putFlowAttributes(provideStep);
assert provideExtStep != null;
final TransportedMessage m = TransportedMessage.newMessage(service, provideExtStep, mex);
// let's use the context of the client
final ChannelHandlerContext ctx = client.getDomainContext();
// it can't be null because it would mean that the component is stopped and in that case we
// wouldn't be receiving messages!
assert ctx != null;
sendToChannel(ctx, m, exchange);
}
/**
* Connect to the provider partner
*
* @param force
* if true
, then if we are already connected, we will first be disconnected
*/
public void connect(final boolean force) {
client.connect(force);
}
/**
* Disconnect from the provider partner
*/
public void disconnect() {
client.disconnect();
}
public JbiProviderDomain getJPD() {
return jpd;
}
public void close() {
// this is like a disconnect... but emanating from the other side
updatePropagatedServices(TransportedPropagations.EMPTY);
}
@Override
protected void logAfterReceivingFromChannel(final TransportedMessage m) {
if (m.step == 2) {
// the message contains the FA we created before sending it as a TransportedNewMessage in send
// this is the end of provides ext that started in ProviderDomain.send
StepLogHelper.addMonitExtEndOrFailureTrace(logger, m.exchange, m.provideExtStep, false);
}
// TODO for now, when the exchange is received from the channel, we set the flow attributes in the context to
// the one of the provide and not the one of the provide ext, but the TRACE is done using the provide ext flow
// attribute: this is ok because only the instance is really important when logging, but who knows if in the
// future things won't be different?!
}
@Override
protected void logBeforeSendingToChannel(final TransportedMessage m) {
final FlowAttributes provideStep = PetalsExecutionContext.getFlowAttributes();
// this is the step of the provide ext
PetalsExecutionContext.putFlowAttributes(m.provideExtStep);
if (m.step == 1) {
logger.log(Level.MONIT, "",
new BcGatewayProvideExtFlowStepBeginLogData(m.provideExtStep, provideStep, jpd.getId()));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy