/**
* Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed 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.wso2.carbon.mediation.registry;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.impl.llom.OMDocumentImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.SynapseException;
import org.apache.synapse.config.SynapseConfigUtils;
import org.apache.synapse.registry.AbstractRegistry;
import org.apache.synapse.registry.RegistryEntry;
import org.wso2.carbon.mediation.registry.persistence.PersistenceManager;
import org.wso2.carbon.mediation.registry.persistence.dataobject.RegistryEntryDO;
import org.wso2.carbon.registry.core.RegistryConstants;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Properties;
/**
* Registry implementation for ESB. This assumes that registry resources can be accessed using FILE
* system or http. Registry meta data are accessed using web services.
*/
public class ESBRegistry extends AbstractRegistry {
public static final int FILE = 100;
public static final int HTTP = 101;
public static final int HTTPS = 102;
public static final String URL_SEPARATOR = "/";
public static final char URL_SEPARATOR_CHAR = '/';
private static final int DELETE_RETRY_SLEEP_TIME = 10;
private static final long DEFAULT_CACHABLE_DURATION = 0;
private static final Log log = LogFactory.getLog(ESBRegistry.class);
private static final int MAX_KEYS = 200;
/**
* File system path corresponding to the FILE url path. This is a system depending path
* used for accessing resources as files.
*/
private String localRegistry = null;
/**
* URL of the WS endpoind to get data about registry entries.
*/
private String metaDataService = null;
/**
* Specifies whether the registry is in the local host or a remote registry.
* Local host means the same computer as ESB is running.
*/
private int registryType = ESBRegistryConstants.LOCAL_HOST_REGISTRY;
/**
* Contains the protocol for the registry. Allowd values are FILE, HTTP and HTTPS.
*/
private int registryProtocol = FILE;
public ESBRegistry() {
this.localRegistry = RegistryHelper.getHome();
}
public void init(Properties properties) {
super.init(properties);
for (Object o : properties.keySet()) {
if (o != null) {
String name = (String) o;
String value = (String) properties.get(name);
addConfigProperty(name, value);
}
}
}
public OMNode lookup(String key) {
if (log.isDebugEnabled()) {
log.info("==> Repository fetch of resource with key : " + key);
}
URLConnection urlc;
URL url = null;
try {
url = new URL(getRoot() + key);
} catch (MalformedURLException e) {
handleException("Invalid path '" + getRoot() + key + "' for URL", e);
}
if ("file".equals(url.getProtocol())) {
try {
url.openStream();
} catch (IOException ignored) {
if (!localRegistry.endsWith(URL_SEPARATOR)) {
localRegistry = localRegistry + URL_SEPARATOR;
}
try {
url = new URL(url.getProtocol() + ":" + localRegistry + key);
} catch (MalformedURLException e) {
handleException("Invalid path '" + url.getProtocol() + ":" +
localRegistry + key + "' for URL", e);
}
try {
url.openStream();
} catch (IOException e) {
return null;
}
}
}
try {
urlc = url.openConnection();
urlc.connect();
} catch (IOException e) {
return null;
}
InputStream input = null;
try {
input = urlc.getInputStream();
} catch (IOException e) {
handleException("Error when getting a stream from the URL", e);
}
if (input == null) {
return null;
}
BufferedInputStream inputStream = new BufferedInputStream(input);
OMNode result = null;
try {
XMLStreamReader parser = XMLInputFactory.newInstance().
createXMLStreamReader(inputStream);
StAXOMBuilder builder = new StAXOMBuilder(parser);
result = builder.getDocumentElement();
} catch (OMException ignored) {
if (log.isDebugEnabled()) {
log.debug("The resource at the provided URL isn't " +
"well-formed XML,So,takes it as a text");
}
try {
inputStream.close();
} catch (IOException e) {
log.error("Error in closing the input stream. ", e);
}
result = SynapseConfigUtils.readNonXML(url);
} catch (XMLStreamException ignored) {
if (log.isDebugEnabled()) {
log.debug("The resource at the provided URL isn't " +
"well-formed XML,So,takes it as a text");
}
try {
inputStream.close();
} catch (IOException e) {
log.error("Error in closing the input stream. ", e);
}
result = SynapseConfigUtils.readNonXML(url);
} finally {
try {
result.detach();
OMDocumentImpl parent = new OMDocumentImpl(OMAbstractFactory.getOMFactory());
parent.addChild(result);
inputStream.close();
} catch (IOException e) {
log.error("Error in closing the input stream.", e);
}
}
return result;
}
public RegistryEntry getRegistryEntry(String key) {
// get information from the actual resource
MediationRegistryEntryImpl entryEmbedded = new MediationRegistryEntryImpl();
try {
URL url = new URL(getRoot() + key);
if ("file".equals(url.getProtocol())) {
try {
url.openStream();
} catch (IOException ignored) {
if (!localRegistry.endsWith(URL_SEPARATOR)) {
localRegistry = localRegistry + URL_SEPARATOR;
}
url = new URL(url.getProtocol() + ":" + localRegistry + key);
try {
url.openStream();
} catch (IOException e) {
return null;
}
}
}
URLConnection urlc = url.openConnection();
entryEmbedded.setKey(key);
entryEmbedded.setName(url.getFile());
entryEmbedded.setType(ESBRegistryConstants.FILE);
entryEmbedded.setDescription("Resource at : " + url.toString());
entryEmbedded.setLastModified(urlc.getLastModified());
entryEmbedded.setVersion(urlc.getLastModified());
if (urlc.getExpiration() > 0) {
entryEmbedded.setCachableDuration(
urlc.getExpiration() - System.currentTimeMillis());
} else {
entryEmbedded.setCachableDuration(getCachableDuration());
}
} catch (MalformedURLException e) {
handleException("Invalid URL reference " + getRoot() + key, e);
} catch (IOException e) {
handleException("IO Error reading from URL " + getRoot() + key, e);
}
// get information from the database
PersistenceManager persistenceManager = PersistenceManager.getInstance();
RegistryEntryDO registryEntryDO = persistenceManager.getRegistryEntry(key);
if (registryEntryDO != null) {
if (registryEntryDO.getExpiryTime() != null) {
entryEmbedded.setCachableDuration(registryEntryDO.getExpiryTime());
} else {
entryEmbedded.setCachableDuration(0);
}
}
return entryEmbedded;
}
/**
* Updates the metadata of the given registry entry
*
* @param entry RegistryEntry containing the new metadata
*/
public void updateRegistryEntry(RegistryEntry entry) {
RegistryEntryDO registryEntryDO = new RegistryEntryDO();
registryEntryDO.setRegistryKey(entry.getKey());
registryEntryDO.setExpiryTime(entry.getCachableDuration());
PersistenceManager persistenceManager = PersistenceManager.getInstance();
persistenceManager.saveOrUpdateRegistryEntry(registryEntryDO);
}
/**
* Updates the registry resource pointed by the given key.
*
* @param key Key of the resource to be updated
* @param value New value of the resource
*/
public void updateResource(String key, Object value) {
if (registryType == ESBRegistryConstants.LOCAL_HOST_REGISTRY) {
File file = new File(localRegistry + RegistryHelper.getSystemDependentPath(key));
if (file.exists()) {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(file));
writer.write(value.toString());
writer.flush();
} catch (IOException e) {
handleException("Couldn't write to registry entry: " + key, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ignored) {
}
}
}
}
} else {
handleException("Remote registry is not supported.");
}
}
/**
* Adds a new resource to the registry.
*
* @param parentName Key of the parent of the new resource
* @param resourceName Name of the new resource
* @param isLeaf Specifies whether the new resource is a leaf or not. In a FILE system based
* registry, leaf is a FILE and non-leaf is a FOLDER.
* @throws Exception if an error occurs while creating the resources
*/
private void addResource(String parentName,
String resourceName, boolean isLeaf) throws Exception {
if (registryType == ESBRegistryConstants.LOCAL_HOST_REGISTRY) {
if (isLeaf) {
createFile(RegistryHelper.getSystemDependentPath(parentName), resourceName);
} else {
createFolder(RegistryHelper.getSystemDependentPath(parentName), resourceName);
}
}
}
/**
* Removes the registry resource identified by the given key. If the key points to a directory,
* all its subdirectories and files in those directoris will be deleted. All the database
* entries for deleted registry resources will be removed.
*
* @param key resource key
*/
private void removeResource(String key) {
if (registryType == ESBRegistryConstants.LOCAL_HOST_REGISTRY) {
File resource = new File(localRegistry + RegistryHelper.getSystemDependentPath(key));
if (resource.exists()) {
if (resource.isFile()) {
deleteFile(resource);
} else if (resource.isDirectory()) {
deleteDirectory(resource);
}
} else {
throw new SynapseException("Parent folder: " + key + " does not exists.");
}
}
}
/**
* Returns the registry key in URL style (with "/" as the separator) for a given FILE.
*
* @param file File to get the key
* @return Registry key
*/
private String getRegsitryKey(File file) {
String path = file.getAbsolutePath();
String rootPath = new File(localRegistry).getAbsolutePath();
return getURLPath(path.substring(rootPath.length() + 1)); // we have remove the preceeding "/"
}
private void deleteFile(File file) {
boolean success = file.delete();
if (!success) {
// try with this work around to overcome a known bug in windows
// work around:
// run garbage collector and sleep for some time and delete
// if still didn't delete,
// rename FILE to a temp FILE in "temp" dir and mark it to delete on exist
System.gc();
try {
Thread.sleep(DELETE_RETRY_SLEEP_TIME);
} catch (InterruptedException e) {
// ignore the exception
}
success = file.delete();
if (!success) {
int suffix = 1;
File renamedFile;
File tempDir = new File("temp");
if (!tempDir.exists()) {
tempDir.mkdir();
}
do {
String changedName = "d" + suffix + file.getName();
renamedFile = new File(tempDir, changedName);
suffix++;
} while (renamedFile.exists());
if (file.renameTo(renamedFile)) {
renamedFile.deleteOnExit();
} else {
handleException("Cannot delete the resource: " + file.getName());
}
}
}
PersistenceManager persistenceManager = PersistenceManager.getInstance();
persistenceManager.deleteRegistryEntry(getRegsitryKey(file));
}
private void deleteDirectory(File dir) {
File[] children = dir.listFiles();
for (File aChildren : children) {
if (aChildren != null) {
if (aChildren.isFile()) {
deleteFile(aChildren);
} else if (aChildren.isDirectory()) {
deleteDirectory(aChildren);
}
}
}
boolean success = dir.delete();
if (success) {
PersistenceManager persistenceManager = PersistenceManager.getInstance();
persistenceManager.deleteRegistryEntry(getRegsitryKey(dir));
} else {
handleException("Cannot delete the resource: " + dir.getName());
}
}
private void createFolder(String parentName, String newFolderName) throws Exception {
/*
search for parent. if found, create the new FOLDER in it. this depends on whether we are
using a remote FILE system or not.
add entry 'parentName/newFolderName' as key to the database.
*/
if (registryType == ESBRegistryConstants.LOCAL_HOST_REGISTRY) {
File parent = new File(localRegistry + parentName);
if (parent.exists()) {
File newEntry = new File(parent, newFolderName);
boolean success = newEntry.mkdir();
if (!success) {
handleException("Couldn't create folder: " + newFolderName);
}
// update meta data to the database
// note that we are using the registry key part (path without the local registry
// root as the key in db
// if the new FOLDER has a parent FOLDER, use its values for defaults for all
// possible properties
RegistryEntryDO registryEntryDO = new RegistryEntryDO();
registryEntryDO.setRegistryKey(parentName + URL_SEPARATOR + newFolderName);
PersistenceManager persistenceManager = PersistenceManager.getInstance();
RegistryEntryDO parentEntryDO =
persistenceManager.getRegistryEntry(getURLPath(parentName));
if (parentEntryDO != null) {
registryEntryDO.setExpiryTime(parentEntryDO.getExpiryTime());
} else {
registryEntryDO.setExpiryTime(getCachableDuration());
}
persistenceManager.addRegistryEntry(registryEntryDO);
} else {
handleException("Parent folder: " + parentName + " does not exists.");
}
}
}
private void createFile(String parentName, String newFileName) throws Exception {
/*
search for parent. if found, create the new FOLDER in it. this depends on whether we are
using a remote FILE system or not.
*/
if (registryType == ESBRegistryConstants.LOCAL_HOST_REGISTRY) {
File parent = new File(localRegistry + parentName);
if (parent.exists()) {
File newFile = new File(parent, newFileName);
boolean success = newFile.createNewFile();
if (!success) {
handleException("Couldn't create resource: " + newFileName);
}
// update meta data to the database
// note that we are using the registry key part (path without the local registry
// root as the key in db
// update the db only if we have some thing else to write other than the key
RegistryEntryDO registryEntryDO = new RegistryEntryDO();
registryEntryDO.setRegistryKey(parentName + URL_SEPARATOR + newFileName);
PersistenceManager persistenceManager = PersistenceManager.getInstance();
RegistryEntryDO parentEntryDO =
persistenceManager.getRegistryEntry(getURLPath(parentName));
if (parentEntryDO != null) {
registryEntryDO.setExpiryTime(parentEntryDO.getExpiryTime());
} else {
registryEntryDO.setExpiryTime(getCachableDuration());
}
persistenceManager.addRegistryEntry(registryEntryDO);
} else {
handleException("Parent folder: " + parentName + " does not exists.");
}
}
}
/**
* Configure the ESB registry using registry parameters.
* <p/>
* root: FILE:directory - registry is on local host
* directory is used to access metadata
* <p/>
* root: http/https:location - has to specify one of the following settings
* localRegistry - location of the local registry
* metadataService - url of the service to access metadata
* If none of above parameters are given "registry" FOLDER is taken as the local registry.
*
* @param name name of the config
* @param value value of the config
*/
private void addConfigProperty(String name, String value) {
if (localRegistry == null) {
// registry root should always end with "/"
if (ESBRegistryConstants.LOCAL_REGISTRY_ROOT.endsWith(URL_SEPARATOR)) {
localRegistry = ESBRegistryConstants.LOCAL_REGISTRY_ROOT;
} else {
localRegistry = ESBRegistryConstants.LOCAL_REGISTRY_ROOT + URL_SEPARATOR;
}
}
if (name != null && value != null) {
if (name.equals("root")) {
// root should always end with '/'
// therefore, property keys do not have to begin with '/', which could be misleading
try {
URL url = new URL(value);
if ("file".equals(url.getProtocol())) {
try {
url.openStream();
} catch (IOException ignored) {
if (!localRegistry.endsWith(URL_SEPARATOR)) {
localRegistry = localRegistry + URL_SEPARATOR;
}
url = new URL(url.getProtocol() + ":" + localRegistry + url.getPath());
try {
url.openStream();
} catch (IOException e) {
log.error("Unable to open a connection to url : " + url, e);
}
}
}
if (url.getProtocol().equals("file")) {
registryProtocol = FILE;
registryType = ESBRegistryConstants.LOCAL_HOST_REGISTRY;
if (url.getPath().endsWith(URL_SEPARATOR)) {
localRegistry = RegistryHelper.getSystemDependentPath(url.getPath());
} else {
localRegistry = RegistryHelper.getSystemDependentPath(url.getPath()) + File.separator;
}
} else if (url.getProtocol().equals("http")) {
registryProtocol = HTTP;
} else if (url.getProtocol().equals("https")) {
registryProtocol = HTTPS;
}
if (!value.endsWith(URL_SEPARATOR)) {
value = value + URL_SEPARATOR;
}
} catch (MalformedURLException e) {
// don't set the root if this is not a valid URL
handleException("Registry root should be a valid URL.", e);
}
}
if (name.equals("localRegistry")) {
registryType = ESBRegistryConstants.LOCAL_HOST_REGISTRY;
// registry root always ends with "/"
if (!value.endsWith(File.separator)) {
value = value + File.separator;
}
localRegistry = value;
}
if (name.equals("matadataService")) {
registryType = ESBRegistryConstants.REMOTE_HOST_REGISTRY;
metaDataService = value;
}
// check if host name verification is disabled
if (name.equalsIgnoreCase("disableHostNameVerification")) {
if (value.equalsIgnoreCase("true")) {
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
}
} else {
log.debug("Name and Value must need");
}
}
/**
* Returns the root of the registry. This is always a URL.
*
* @return Registry root.
*/
public String getRoot() {
String root = (String) properties.get("root");
if (root == null) {
return "";
} else {
return root;
}
}
public long getCachableDuration() {
String cachableDuration = (String) properties.get("cachableDuration");
return cachableDuration == null ? DEFAULT_CACHABLE_DURATION : Long.parseLong(cachableDuration);
}
/**
* Gives the children of the given entry. If the registry is in the same host get the children
* (subfolders and files) using the FILE system. If the registry is in a remote host, get
* children using a WS call. Give null or registry entry with "" as the key to list the children
* of the root.
*
* @param entry registry entry to get the children
* @return children of the given entry
*/
public RegistryEntry[] getChildren(RegistryEntry entry) {
String registryRoot = localRegistry;
if (entry == null) {
// give the children of the root
// null or key = "" stands for root
MediationRegistryEntryImpl registryEntry = new MediationRegistryEntryImpl();
registryEntry.setKey("");
entry = registryEntry;
}
if (registryType == ESBRegistryConstants.LOCAL_HOST_REGISTRY) {
// registry is in the local FILE system. access it directly.
String entryPath = RegistryHelper.getSystemDependentPath(entry.getKey());
File file = new File(registryRoot + entryPath);
if (!file.isDirectory()) {
return null;
}
try {
String[] children = file.list();
RegistryEntry[] entries = new RegistryEntry[children.length];
for (int i = 0; i < children.length; i++) {
MediationRegistryEntryImpl registryEntry = new MediationRegistryEntryImpl();
if (entry.getKey().equals("")) {
// user asking for the children of the root
registryEntry.setKey(children[i]);
} else {
if (entryPath.endsWith(URL_SEPARATOR)) {
registryEntry.setKey(getURLPath(entry.getKey() + children[i]));
} else {
registryEntry.setKey(getURLPath(entry.getKey() + URL_SEPARATOR + children[i]));
}
}
// set if the registry entry is a FILE or a FOLDER
File entryFile = new File
(registryRoot + RegistryHelper.getSystemDependentPath(registryEntry.getKey()));
if (entryFile.isDirectory()) {
registryEntry.setType(ESBRegistryConstants.FOLDER);
}
entries[i] = registryEntry;
}
return entries;
} catch (Exception e) {
handleException("Error in reading the URL.");
}
} else if (registryType == ESBRegistryConstants.REMOTE_HOST_REGISTRY) {
// implement for remote registries.
}
return null;
}
public RegistryEntry[] getDescendants(RegistryEntry entry) {
ArrayList<RegistryEntry> list = new ArrayList<RegistryEntry>();
RegistryEntry[] entries = getChildren(entry);
if (entries != null) {
for (RegistryEntry entry1 : entries) {
if (list.size() > MAX_KEYS) {
break;
}
fillDescendants(entry1, list);
}
}
RegistryEntry[] descendants = new RegistryEntry[list.size()];
for (int i = 0; i < list.size(); i++) {
descendants[i] = list.get(i);
}
return descendants;
}
public void delete(String path) {
removeResource(path);
}
public void newResource(String path, boolean isDirectory) {
String parent = getParentPath(path);
String fileName = getResourceName(path);
try {
addResource(parent, fileName, !isDirectory);
} catch (Exception e) {
handleException("Error when adding a new resource", e);
}
}
private void fillDescendants(RegistryEntry parent, ArrayList<RegistryEntry> list) {
RegistryEntry[] entries = getChildren(parent);
if (entries != null) {
for (RegistryEntry entry : entries) {
if (list.size() > MAX_KEYS) {
break;
}
fillDescendants(entry, list);
}
} else {
list.add(parent);
}
}
private String getURLPath(String filePath) {
return filePath.replace(File.separatorChar, URL_SEPARATOR_CHAR);
}
private void handleException(String msg, Exception e) {
log.error(msg, e);
throw new SynapseException(msg, e);
}
private void handleException(String msg) {
log.error(msg);
throw new SynapseException(msg);
}
private String getParentPath(String resourcePath) {
String parentPath;
if (resourcePath.equals(RegistryConstants.ROOT_PATH)) {
parentPath = null;
} else {
if (resourcePath.lastIndexOf(RegistryConstants.PATH_SEPARATOR) == 0) {
parentPath = RegistryConstants.ROOT_PATH;
} else {
parentPath = resourcePath.substring(
0, resourcePath.lastIndexOf(RegistryConstants.PATH_SEPARATOR));
}
}
return parentPath;
}
private String getResourceName(String path) {
if (path != null) {
String correctedPath = path;
if (path.endsWith(RegistryConstants.PATH_SEPARATOR)) {
correctedPath = path.substring(0,
path.lastIndexOf(RegistryConstants.PATH_SEPARATOR));
}
return correctedPath.substring(
correctedPath.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1,
correctedPath.length());
}
return "";
}
}