blob: 250e523b114dd55dcfe59cb873f32e1904d1c7cd [file] [log] [blame]
/**
* 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.cxf.endpoint;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import org.apache.cxf.BusException;
import org.apache.cxf.common.util.PropertyUtils;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.transport.ConduitInitiator;
import org.apache.cxf.transport.ConduitInitiatorManager;
import org.apache.cxf.transport.MessageObserver;
import org.apache.cxf.ws.addressing.AttributedURIType;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
/**
* Abstract base class holding logic common to any ConduitSelector
* that retrieves a Conduit from the ConduitInitiator.
*/
public abstract class AbstractConduitSelector implements ConduitSelector, Closeable {
public static final String CONDUIT_COMPARE_FULL_URL
= "org.apache.cxf.ConduitSelector.compareFullUrl";
protected static final String KEEP_CONDUIT_ALIVE = "KeepConduitAlive";
//collection of conduits that were created so we can close them all at the end
protected List<Conduit> conduits = new CopyOnWriteArrayList<>();
protected Endpoint endpoint;
public AbstractConduitSelector() {
}
/**
* Constructor, allowing a specific conduit to override normal selection.
*
* @param c specific conduit
*/
public AbstractConduitSelector(Conduit c) {
if (c != null) {
conduits.add(c);
}
}
public void close() {
for (Conduit c : conduits) {
c.close();
}
conduits.clear();
}
protected void removeConduit(Conduit conduit) {
if (conduit != null) {
conduit.close();
conduits.remove(conduit);
}
}
/**
* Mechanics to actually get the Conduit from the ConduitInitiator
* if necessary.
*
* @param message the current Message
*/
protected Conduit getSelectedConduit(Message message) {
Conduit c = findCompatibleConduit(message);
if (c == null) {
Exchange exchange = message.getExchange();
EndpointInfo ei = endpoint.getEndpointInfo();
String transportID = ei.getTransportId();
try {
ConduitInitiatorManager conduitInitiatorMgr = exchange.getBus()
.getExtension(ConduitInitiatorManager.class);
if (conduitInitiatorMgr != null) {
ConduitInitiator conduitInitiator =
conduitInitiatorMgr.getConduitInitiator(transportID);
if (conduitInitiator != null) {
c = createConduit(message, exchange, conduitInitiator);
} else {
getLogger().warning("ConduitInitiator not found: "
+ ei.getAddress());
}
} else {
getLogger().warning("ConduitInitiatorManager not found");
}
} catch (BusException | IOException ex) {
throw new Fault(ex);
}
}
if (c != null && c.getTarget() != null && c.getTarget().getAddress() != null) {
replaceEndpointAddressPropertyIfNeeded(message, c.getTarget().getAddress().getValue(), c);
}
//the search for the conduit could cause extra properties to be reset/loaded.
message.resetContextCache();
message.put(Conduit.class, c);
return c;
}
protected Conduit createConduit(Message message, Exchange exchange, ConduitInitiator conduitInitiator)
throws IOException {
Conduit c;
synchronized (endpoint) {
if (!conduits.isEmpty()) {
c = findCompatibleConduit(message);
if (c != null) {
return c;
}
}
EndpointInfo ei = endpoint.getEndpointInfo();
String add = (String)message.get(Message.ENDPOINT_ADDRESS);
String basePath = (String)message.get(Message.BASE_PATH);
if (StringUtils.isEmpty(add)
|| add.equals(ei.getAddress())) {
c = conduitInitiator.getConduit(ei, exchange.getBus());
replaceEndpointAddressPropertyIfNeeded(message, add, c);
} else {
EndpointReferenceType epr = new EndpointReferenceType();
AttributedURIType ad = new AttributedURIType();
ad.setValue(StringUtils.isEmpty(basePath) ? add : basePath);
epr.setAddress(ad);
c = conduitInitiator.getConduit(ei, epr, exchange.getBus());
}
MessageObserver observer =
exchange.get(MessageObserver.class);
if (observer != null) {
c.setMessageObserver(observer);
} else {
getLogger().warning("MessageObserver not found");
}
conduits.add(c);
}
return c;
}
// Some conduits may replace the endpoint address after it has already been prepared
// but before the invocation has been done (ex, org.apache.cxf.clustering.LoadDistributorTargetSelector)
// which may affect JAX-RS clients where actual endpoint address property may include additional path
// segments.
protected boolean replaceEndpointAddressPropertyIfNeeded(Message message,
String endpointAddress,
Conduit cond) {
return false;
}
/**
* @return the encapsulated Endpoint
*/
public Endpoint getEndpoint() {
return endpoint;
}
/**
* @param ep the endpoint to encapsulate
*/
public void setEndpoint(Endpoint ep) {
endpoint = ep;
}
/**
* Called on completion of the MEP for which the Conduit was required.
*
* @param exchange represents the completed MEP
*/
public void complete(Exchange exchange) {
// Clients expecting explicit InputStream responses
// will need to keep low level conduits operating on InputStreams open
// and will be responsible for closing the streams
if (PropertyUtils.isTrue(exchange.get(KEEP_CONDUIT_ALIVE))) {
return;
}
try {
if (exchange.getInMessage() != null) {
Conduit c = exchange.getOutMessage().get(Conduit.class);
if (c == null) {
getSelectedConduit(exchange.getInMessage()).close(exchange.getInMessage());
} else {
c.close(exchange.getInMessage());
}
}
} catch (IOException e) {
//IGNORE
}
}
/**
* @return the logger to use
*/
protected abstract Logger getLogger();
/**
* If address protocol was changed, conduit should be re-initialised
*
* @param message the current Message
*/
protected Conduit findCompatibleConduit(Message message) {
Conduit c = message.get(Conduit.class);
if (c == null
&& message.getExchange() != null
&& message.getExchange().getOutMessage() != null
&& message.getExchange().getOutMessage() != message) {
c = message.getExchange().getOutMessage().get(Conduit.class);
}
if (c != null) {
return c;
}
ContextualBooleanGetter cbg = new ContextualBooleanGetter(message);
for (Conduit c2 : conduits) {
if (c2.getTarget() == null
|| c2.getTarget().getAddress() == null
|| c2.getTarget().getAddress().getValue() == null) {
continue;
}
String conduitAddress = c2.getTarget().getAddress().getValue();
EndpointInfo ei = endpoint.getEndpointInfo();
String actualAddress = ei.getAddress();
String messageAddress = (String)message.get(Message.ENDPOINT_ADDRESS);
if (messageAddress != null) {
actualAddress = messageAddress;
}
if (matchAddresses(conduitAddress, actualAddress, cbg)) {
return c2;
}
}
for (Conduit c2 : conduits) {
if (c2.getTarget() == null
|| c2.getTarget().getAddress() == null
|| c2.getTarget().getAddress().getValue() == null) {
return c2;
}
}
return null;
}
private boolean matchAddresses(String conduitAddress, String actualAddress, ContextualBooleanGetter cbg) {
if (conduitAddress.length() == actualAddress.length()) {
//let's be optimistic and try full comparison first, regardless of CONDUIT_COMPARE_FULL_URL value,
//which can be expensive to fetch; as a matter of fact, anyway, if the addresses fully match,
//their hosts also match
if (conduitAddress.equalsIgnoreCase(actualAddress)) {
return true;
}
return !cbg.isFullComparison() && matchAddressSubstrings(conduitAddress, actualAddress);
}
return !cbg.isFullComparison() && matchAddressSubstrings(conduitAddress, actualAddress);
}
//smart address substring comparison that tries to avoid building and comparing substrings unless strictly required
private boolean matchAddressSubstrings(String conduitAddress, String actualAddress) {
int idx = conduitAddress.indexOf(':');
if (idx == actualAddress.indexOf(':')) {
if (idx <= 0) {
return true;
}
return conduitAddress.substring(0, idx).equalsIgnoreCase(actualAddress.substring(0, idx));
}
//no possible match as for sure the substrings before idx will be different
return false;
}
private static final class ContextualBooleanGetter {
private Boolean value;
private final Message message;
ContextualBooleanGetter(Message message) {
this.message = message;
}
public boolean isFullComparison() {
if (value == null) {
value = MessageUtils.getContextualBoolean(message, CONDUIT_COMPARE_FULL_URL, false);
}
return value;
}
}
}