/*
* 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.startup.tasks;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.SynapseException;
import org.apache.synapse.commons.jmx.MBeanRegistrar;
import org.apache.synapse.config.Entry;
import org.apache.synapse.config.SynapseConfiguration;
import org.apache.synapse.config.xml.MediatorFactoryFinder;
import org.apache.synapse.config.xml.endpoints.XMLToEndpointMapper;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.endpoints.Endpoint;
import org.apache.synapse.mediators.base.SequenceMediator;
import org.apache.synapse.registry.AbstractRegistry;
import org.apache.synapse.registry.RegistryEntry;
import org.apache.synapse.task.Task;
import javax.xml.namespace.QName;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Items is a xml configuration
* <resources>
* <resource type="sequence | endpoint | xml | text">registry path</resource>
* </resources>
*/
public class RegistryResourceFetcher implements Task, ManagedLifecycle {
private static Log log = LogFactory.getLog(RegistryResourceFetcher.class);
public static final String SEQUENCE = "sequence";
public static final String ENDPOINT = "endpoint";
public static final String XML = "xml";
public static final String TEXT = "text";
private OMElement items;
private List<RegistryResourceEntry> registryResources = new ArrayList<RegistryResourceEntry>();
private SynapseConfiguration synapseConfiguration = null;
private SynapseEnvironment synapseEnvironment = null;
private AbstractRegistry registry = null;
private int backOffFactor = 1;
private int maxSuspendThreshold = 100;
private int suspendThreshold = 4;
private int currentFailedCount = 0;
private int executionCount = 0;
private int nextSuspendExecutionCount = 1;
private long lastExecutionTime = 0;
private State state = State.INIT;
private ReadWriteLock lock = new ReentrantReadWriteLock();
private RegistryResourceFetcherView view = null;
public OMElement getItems() {
return items;
}
public void setItems(OMElement items) {
this.items = items;
}
public int getSuspendThreshold() {
return suspendThreshold;
}
public int getMaxSuspendThreshold() {
return maxSuspendThreshold;
}
public int getBackOffFactor() {
return backOffFactor;
}
public void setBackOffFactor(int backOffFactor) {
this.backOffFactor = backOffFactor;
}
public void setMaxSuspendThreshold(int maxSuspendThreshold) {
this.maxSuspendThreshold = maxSuspendThreshold;
}
public void setSuspendThreshold(int suspendThreshold) {
this.suspendThreshold = suspendThreshold;
}
public void init(SynapseEnvironment se) {
if (items == null) {
String msg = "resources configuration is required";
log.error(msg);
throw new SynapseException(msg);
}
Iterator it = items.getChildrenWithName(new QName("resource"));
while (it.hasNext()) {
OMElement resourceElement = (OMElement) it.next();
String path = resourceElement.getText();
String type = "xml";
OMAttribute typeAttribute = resourceElement.getAttribute(new QName("type"));
if (typeAttribute != null) {
type = typeAttribute.getAttributeValue();
}
registryResources.add(new RegistryResourceEntry(path, type));
}
this.synapseConfiguration = se.getSynapseConfiguration();
this.registry = (AbstractRegistry) se.getSynapseConfiguration().getRegistry();
this.synapseEnvironment = se;
this.view = new RegistryResourceFetcherView(this);
MBeanRegistrar.getInstance().registerMBean(view, "ESB-Registry", "RegistryResourceFetcher");
this.state = State.ACTIVE;
}
public void destroy() {
MBeanRegistrar.getInstance().unRegisterMBean("ESB-Registry", "RegistryResourceFetcher");
}
public void execute() {
Lock readerLock = lock.readLock();
readerLock.lock();
try {
boolean execute = false;
executionCount++;
if (state == State.SUSPENDED) {
if (executionCount >= maxSuspendThreshold) {
execute = true;
}
} else if (state == State.BACK_OFF) {
if (nextSuspendExecutionCount == executionCount) {
nextSuspendExecutionCount = nextSuspendExecutionCount * backOffFactor;
execute = true;
}
} else if (state == State.SUSPECT || state == State.ACTIVE) {
execute = true;
}
if (!execute) {
if (log.isDebugEnabled()) {
log.debug("Skipping the execution because the Registry Fetching is at SUSPENDED state");
}
return;
}
for (RegistryResourceEntry key : registryResources) {
if (state == State.ACTIVE) {
Entry entry = synapseConfiguration.getEntryDefinition(key.getPath());
if (entry == null) {
log.warn("A non remote entry has being specified: " + key.getPath());
return;
}
if (key.getType().equals(SEQUENCE)) {
entry.setMapper(MediatorFactoryFinder.getInstance());
} else if (key.getType().equals(ENDPOINT)) {
entry.setMapper(XMLToEndpointMapper.getInstance());
}
fetchEntry(key.getPath());
}
}
lastExecutionTime = System.currentTimeMillis();
} finally {
readerLock.unlock();
}
}
private class RegistryResourceEntry {
private String path;
private String type;
private RegistryResourceEntry(String path, String type) {
this.path = path;
this.type = type;
}
public String getPath() {
return path;
}
public String getType() {
return type;
}
}
/**
* Get the resource with the given key
*
* @param key the key of the resource required
*/
private void fetchEntry(String key) {
Map localRegistry = synapseConfiguration.getLocalRegistry();
Object o = localRegistry.get(key);
if (o != null && o instanceof Entry) {
Entry entry = (Entry) o;
// This must be a dynamic entry whose cache has expired or which is not cached at all
// A registry lookup is in order
if (registry != null) {
if (entry.isCached()) {
try {
Object resource = getResource(entry, synapseConfiguration.getProperties());
if (resource == null) {
log.warn("Failed to load the resource at the first time, " +
"non-existing resource: " + key);
} else {
entry.setExpiryTime(Long.MAX_VALUE);
}
onSuccess();
} catch (Exception e) {
// Error occured while loading the resource from the registry
// Fall back to the cached value - Do not increase the expiry time
log.warn("Error while loading the resource " + key + " from the remote " +
"registry. Previously cached value will be used. Check the " +
"registry accessibility.");
onError();
}
} else {
try {
// Resource not available in the cache - Must load from the registry
// No fall backs possible here!!
Object resource = getResource(entry, synapseConfiguration.getProperties());
if (resource == null) {
log.warn("Failed to load the resource at the first time, " +
"non-existing resource: " + key);
} else {
entry.setExpiryTime(Long.MAX_VALUE);
}
} catch (Exception e) {
// failed to get the resource for the first time
log.warn("Failed to load the resource at the first time, " +
"non-existing resource: " + key);
}
}
} else {
if (entry.isCached()) {
// Fall back to the cached value
log.warn("The registry is no longer available in the Synapse configuration. " +
"Using the previously cached value for the resource : " + key);
} else {
if (log.isDebugEnabled()) {
log.debug("Will not evaluate the value of the remote entry with a key "
+ key + ", because the registry is not available");
}
}
}
}
}
private Object getResource(Entry entry, Properties properties) {
OMNode omNode;
RegistryEntry re = registry.getRegistryEntry(entry.getKey());
omNode = registry.lookup(entry.getKey());
if (re == null) {
return null;
}
if ((!entry.isCached() || (re.getVersion() == Long.MIN_VALUE ||
re.getVersion() != entry.getVersion())) || re.getLastModified() >= lastExecutionTime) {
entry.setEntryProperties(registry.getResourceProperties(entry.getKey()));
entry.setVersion(re.getVersion());
// if we get here, we have received the raw omNode from the
// registry and our previous copy (if we had one) has expired or is not valid
Object expiredValue = entry.getValue();
// if we have a XMLToObjectMapper for this entry, use it to convert this
// resource into the appropriate object - e.g. sequence or endpoint
if (entry.getMapper() != null) {
entry.setValue(entry.getMapper().getObjectFromOMNode(omNode, properties));
if (entry.getValue() instanceof SequenceMediator) {
SequenceMediator seq = (SequenceMediator) entry.getValue();
seq.setDynamic(true);
seq.setRegistryKey(entry.getKey());
seq.init(synapseEnvironment);
} else if (entry.getValue() instanceof Endpoint) {
Endpoint ep = (Endpoint) entry.getValue();
ep.init(synapseEnvironment);
}
} else {
// if the type of the object is known to have a mapper, create the
// resultant Object using the known mapper, and cache this Object
// else cache the raw OMNode
entry.setValue(omNode);
}
if (expiredValue != null) {
// Destroy the old resource so that everything is properly cleaned up
if (expiredValue instanceof SequenceMediator) {
((SequenceMediator) expiredValue).destroy();
} else if (expiredValue instanceof Endpoint) {
((Endpoint) expiredValue).destroy();
}
}
entry.setVersion(re.getVersion());
}
// renew cache lease for another cachable duration (as returned by the
// new getRegistryEntry() call
if (re.getCachableDuration() > 0) {
entry.setExpiryTime(
System.currentTimeMillis() + re.getCachableDuration());
} else {
entry.setExpiryTime(-1);
}
return entry.getValue();
}
private void onError() {
currentFailedCount++;
if (state == State.SUSPECT) {
if (currentFailedCount == suspendThreshold) {
log.info("Registry fetching state moved to :" + State.BACK_OFF +
" Registry is no longer available & Cached values will be used");
state = State.BACK_OFF;
executionCount = 0;
nextSuspendExecutionCount = 1;
}
} else if (state == State.BACK_OFF) {
if (executionCount >= maxSuspendThreshold) {
log.info("Registry fetching state moved to :" + State.SUSPENDED +
" Will be retried in another " + maxSuspendThreshold);
state = State.SUSPENDED;
executionCount = 0;
nextSuspendExecutionCount = 1;
}
} else if (state == State.SUSPENDED) {
// we remain in the same stage
state = State.SUSPENDED;
executionCount = 0;
} else if (state == State.ACTIVE) {
// we move to the SUSPECT stage
log.info("Registry fetching state moved to :" + State.SUSPECT +
" Registry seems to be no longer available & Cached values will be used");
state = State.SUSPECT;
}
}
private void onSuccess() {
currentFailedCount = 0;
if (state != State.ACTIVE) {
log.info("Registry state changed from: " + state + " " + State.ACTIVE);
}
state = State.ACTIVE;
}
public void setState(State state) {
Lock writeLock = lock.writeLock();
writeLock.lock();
try {
if (state == State.ACTIVE) {
currentFailedCount = 0;
executionCount = 0;
nextSuspendExecutionCount = 1;
lastExecutionTime = 0;
} else if (state == State.SUSPENDED) {
currentFailedCount = 0;
executionCount = 0;
nextSuspendExecutionCount = 0;
}
this.state = state;
} finally {
writeLock.unlock();
}
}
public State getState() {
return state;
}
public void reset() {
log.info("Reset the state to the initial values");
state = State.ACTIVE;
currentFailedCount = 0;
executionCount = 0;
nextSuspendExecutionCount = 1;
lastExecutionTime = 0;
}
}