/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.synapse.endpoints;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.clustering.Member;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.SynapseException;
import org.apache.synapse.aspects.ComponentType;
import org.apache.synapse.aspects.flow.statistics.collectors.CloseEventCollector;
import org.apache.synapse.aspects.flow.statistics.collectors.OpenEventCollector;
import org.apache.synapse.aspects.flow.statistics.collectors.RuntimeStatisticCollector;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.Value;
import org.apache.synapse.mediators.eip.EIPConstants;
import org.apache.synapse.transport.passthru.util.RelayUtils;
import org.apache.synapse.util.MessageHelper;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
/**
* @author nuwan
* <p>
* A Recipient List endpoint can contain multiple child endpoints or member elements.
* It routes cloned copies of messages to each child recipient. This will assume that
* all immediate child endpoints are identical in state (state is replicated) or state
* is not maintained at those endpoints.
* </p>
*/
public class RecipientListEndpoint extends AbstractEndpoint {
private static final Log log = LogFactory.getLog(RecipientListEndpoint.class);
private static final String DELIMETER = ",";
/**
* The list of members to which the message is delivered to
*/
private List<Member> members;
private Map<String,Endpoint> dynamicEndpointPool ;
private Value dynamicEnpointSet;
public final static int DEFAULT_MAX_POOL = 20;
/**
* Should this recipient list failover;
*/
private boolean failover;
private int currentPool;
private SynapseEnvironment env = null;
public RecipientListEndpoint(int poolsize){
dynamicEndpointPool = Collections.synchronizedMap(new DynamicEndpointPool<String, Endpoint>(poolsize));
this.currentPool = poolsize;
}
public RecipientListEndpoint(){
this.currentPool = DEFAULT_MAX_POOL;
}
@Override
public void init(SynapseEnvironment synapseEnvironment) {
if (!initialized) {
super.init(synapseEnvironment);
}
this.env = synapseEnvironment;
this.setContentAware(true);
}
@Override
public void destroy() {
super.destroy();
}
@Override
public void send(MessageContext synCtx) {
if (RuntimeStatisticCollector.isStatisticsEnabled()) {
Integer currentIndex = null;
boolean retry = (synCtx.getProperty(SynapseConstants.LAST_ENDPOINT) != null);
if ((getDefinition() != null) && !retry) {
currentIndex = OpenEventCollector.reportChildEntryEvent(synCtx, getReportingName(),
ComponentType.ENDPOINT, getDefinition().getAspectConfiguration(), true);
}
try {
sendMessage(synCtx);
} finally {
if (currentIndex != null) {
CloseEventCollector.closeEntryEvent(synCtx, getReportingName(),
ComponentType.MEDIATOR, currentIndex, false);
}
}
} else {
sendMessage(synCtx);
}
}
public void sendMessage(MessageContext synCtx) {
logSetter();
if (log.isDebugEnabled()) {
log.debug("Sending using Recipient List " + toString());
}
if (getContext().isState(EndpointContext.ST_OFF)) {
informFailure(synCtx, SynapseConstants.ENDPOINT_RL_NONE_READY,
"RecipientList endpoint : " + getName() != null ? getName() : SynapseConstants.ANONYMOUS_ENDPOINT + " - is inactive");
return;
}
List<Endpoint> children = getChildren();
//Service child endpoints
if (children != null && !children.isEmpty()) {
sendToEndpointList(synCtx, children);
}
//Service member elements if specified
else if (members != null && !members.isEmpty()) {
sendToApplicationMembers(synCtx);
}
else if (dynamicEnpointSet != null) {
sendToDynamicMembers(synCtx);
}
else {
String msg = "No child endpoints nor member elements available";
log.error(msg);
throw new SynapseException(msg);
}
}
private void sendToEndpointList(MessageContext synCtx, List<Endpoint> children) {
int i = 0;
boolean foundEndpoint = false;
//we should build the message, its should have the same behavior as clone mediator
try {
RelayUtils.buildMessage(((Axis2MessageContext) synCtx).getAxis2MessageContext(),false);
} catch (Exception e) {
handleException("Error while building message", e);
}
for (Endpoint childEndpoint : children) {
if (childEndpoint.readyToSend()) {
foundEndpoint = true;
MessageContext newCtx = null;
try {
newCtx = MessageHelper.cloneMessageContext(synCtx);
} catch (AxisFault e) {
handleException("Error cloning the message context", e);
}
//Used when aggregating responses
newCtx.setProperty(EIPConstants.MESSAGE_SEQUENCE,
String.valueOf(i++) + EIPConstants.MESSAGE_SEQUENCE_DELEMITER +
children.size());
// evaluate the endpoint properties
evaluateProperties(newCtx);
newCtx.pushFaultHandler(this);
try {
childEndpoint.send(newCtx);
} catch (SynapseException e) {
String msg ="Child Endpoint " + (childEndpoint.getName() != null ? childEndpoint.getName() : SynapseConstants.ANONYMOUS_ENDPOINT) +
" of Recipient List endpoint " + (getName() != null ? getName() : SynapseConstants.ANONYMOUS_ENDPOINT) + " encountered an error while sending the message";
log.warn(msg);
continue; //continue sending message to rest of the child endpoints.
}
}
}
if (!foundEndpoint) {
String msg = "Recipient List endpoint : " +
(getName() != null ? getName() : SynapseConstants.ANONYMOUS_ENDPOINT) +
" - no ready child endpoints";
log.warn(msg);
informFailure(synCtx, SynapseConstants.ENDPOINT_RL_NONE_READY, msg);
}
}
private void sendToDynamicMembers(MessageContext synCtx) {
String dynamicUrlStr = dynamicEnpointSet.evaluateValue(synCtx);
String[] dynamicUrlSet = dynamicUrlStr.split(DELIMETER);
if (dynamicUrlSet.length == 0) {
log.warn("No recipient/s was derived from the expression : " + dynamicEnpointSet.toString());
return;
}
List<Endpoint> children = new ArrayList<Endpoint>();
for (String url : dynamicUrlSet) {
if (url != null && !"".equals(url.trim())) {
//get an Endpoint from the pool
Endpoint epFromPool = dynamicEndpointPool.get(url);
if (epFromPool == null) {
AddressEndpoint endpoint = new AddressEndpoint();
endpoint.setEnableMBeanStats(false);
endpoint.setName("DYNAMIC_RECIPIENT_LIST_EP_" + UUID.randomUUID());
EndpointDefinition definition = new EndpointDefinition();
definition.setReplicationDisabled(true);
definition.setAddress(url);
endpoint.setDefinition(definition);
endpoint.init(env);
//finally add the newly created endpoint to the Pool
dynamicEndpointPool.put(url, endpoint);
children.add(endpoint);
} else {
//do nothing endpoint is already in the pool
children.add(epFromPool);
}
}
}
if (children.size() > 0) {
sendToEndpointList(synCtx, children);
} else {
if (log.isDebugEnabled()) {
log.debug("Halted sending messages to recipients. No recipient found !!! : " + dynamicUrlStr);
}
}
}
/**<p>Iterates the <b>members</b> list, creates Address Endpoints
* from each member element and routes cloned copies of the message
* to each Address Endpoint.</p>
* @param synCtx - The Original Message received by Synapse
*/
private void sendToApplicationMembers(MessageContext synCtx){
int i = 0;
boolean foundEndpoint = false;
for (Member member : members) {
org.apache.axis2.context.MessageContext axis2MsgCtx = ((Axis2MessageContext) synCtx)
.getAxis2MessageContext();
String transport = axis2MsgCtx.getTransportIn().getName();
//If the transport is not HTTP nor HTTPS
if (!transport.equals("http") && !transport.equals("https")) {
//Skip member.
log.error("Cannot deliver for non-HTTP/S transport " + transport);
continue;
}
MessageContext newCtx = null;
try {
newCtx = MessageHelper.cloneMessageContext(synCtx);
} catch (AxisFault e) {
handleException("Error cloning the message context", e);
}
// Used when aggregating responses
newCtx.setProperty(
EIPConstants.MESSAGE_SEQUENCE,
String.valueOf(i++)
+ EIPConstants.MESSAGE_SEQUENCE_DELEMITER
+ members.size());
// evaluate the endpoint properties
evaluateProperties(newCtx);
// URL rewrite
String address = newCtx.getTo().getAddress();
if (address.indexOf(":") != -1) {
try {
address = new URL(address).getPath();
} catch (MalformedURLException e) {
String msg = "URL " + address + " is malformed";
log.error(msg, e);
throw new SynapseException(msg, e);
}
}
EndpointReference epr = new EndpointReference(transport
+ "://"
+ member.getHostName()
+ ":"
+ ("http".equals(transport) ? member.getHttpPort()
: member.getHttpsPort()) + address);
newCtx.setTo(epr);
newCtx.pushFaultHandler(this);
AddressEndpoint endpoint = new AddressEndpoint();
EndpointDefinition definition = new EndpointDefinition();
endpoint.setDefinition(definition);
endpoint.init(newCtx.getEnvironment());
if(endpoint.readyToSend()){
foundEndpoint = true;
endpoint.send(newCtx);
}
}
if(!foundEndpoint){
String msg = "Recipient List endpoint : " +
(getName() != null ? getName() : SynapseConstants.ANONYMOUS_ENDPOINT) +
" - no ready child members";
log.warn(msg);
informFailure(synCtx, SynapseConstants.ENDPOINT_RL_NONE_READY, msg);
}
}
@Override
public boolean readyToSend(){
if (getContext().isState(EndpointContext.ST_OFF)) {
return false;
}
for(Endpoint endpoint : getChildren()){
if(endpoint.readyToSend()){
if (log.isDebugEnabled()) {
log.debug("Recipient List " + this.toString()
+ " has at least one endpoint at ready state");
}
return true;
}
}
return false;
}
public void onChildEndpointFail(Endpoint endpoint, MessageContext synMessageContext) {
//we just log the failed recipient here
logOnChildEndpointFail(endpoint, synMessageContext);
String msg = "";
if (log.isDebugEnabled()) {
msg = "Recipient List endpoint : " +
(getName() != null ? getName() : SynapseConstants.ANONYMOUS_ENDPOINT) +
" - one of the recipients encounterd an error while sending the message ";
log.debug(msg);
}
informFailure(synMessageContext,SynapseConstants.ENDPOINT_RL_NONE_READY, msg);
}
public List<Member> getMembers() {
return members;
}
public void setMembers(List<Member> members) {
this.members = members;
}
public boolean isFailover() {
return failover;
}
public void setFailover(boolean failover) {
this.failover = failover;
}
public Value getDynamicEnpointSet() {
return dynamicEnpointSet;
}
public void setDynamicEnpointSet(Value dynamicEnpointSet) {
this.dynamicEnpointSet = dynamicEnpointSet;
}
public int getCurrentPoolSize() {
return currentPool;
}
/**
* create a simple LRU cached Endpoint pool for dynamic endpoints
*/
private static class DynamicEndpointPool<String, Endpoint> extends LinkedHashMap<String, Endpoint> {
private final int maxPoolSize;
public DynamicEndpointPool(final int max) {
super(max + 1, 1.0f, true);
this.maxPoolSize = max;
}
@Override
protected boolean removeEldestEntry(final Map.Entry<String, Endpoint> eldest) {
return super.size() > maxPoolSize;
}
}
}