/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework 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 the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.core.namespace;
import static java.lang.String.format;
import static org.openbel.framework.common.BELUtilities.asPath;
import static org.openbel.framework.common.BELUtilities.nulls;
import static org.openbel.framework.common.PathConstants.NAMESPACE_ROOT_DIRECTORY_NAME;
import static org.openbel.framework.common.Strings.INVALID_SYMBOLS;
import static org.openbel.framework.core.df.cache.ResourceType.NAMESPACES;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Destroyable;
import org.openbel.framework.common.InvalidArgument;
import org.openbel.framework.common.model.Document;
import org.openbel.framework.common.model.Namespace;
import org.openbel.framework.common.model.Parameter;
import org.openbel.framework.common.model.Statement;
import org.openbel.framework.core.compiler.SymbolWarning;
import org.openbel.framework.core.df.cache.CacheLookupService;
import org.openbel.framework.core.df.cache.CacheableResourceService;
import org.openbel.framework.core.df.cache.CachedResource;
import org.openbel.framework.core.df.cache.ResolvedResource;
import org.openbel.framework.core.indexer.IndexingFailure;
import org.openbel.framework.core.indexer.JDBMNamespaceLookup;
import org.openbel.framework.core.protocol.ResourceDownloadError;
/**
* DefaultNamespaceService implements a service to handle all operations for
* working with namespaces.
* <p>
* This class <strong><em>is thread-safe</em></strong> and allows concurrent
* </p>
*
* @author Anthony Bargnesi {@code <abargnesi@selventa.com>}
*/
public final class DefaultNamespaceService implements NamespaceService,
Destroyable {
private static final String NS_INDEX_FILE_NAME = "namespace.index";
private boolean running;
/**
* Defines the map of open namespace indexes. The entries are keyed by
* {@link Namespace} resource location with the {@link JDBMNamespaceLookup} index as
* the value.
* <p>
* This map in thread-safe as it uses {@link ConcurrentHashMap}
* </p>
*/
private final Map<String, JDBMNamespaceLookup> openNamespaces =
new ConcurrentHashMap<String, JDBMNamespaceLookup>();
/**
* Defines the map of available namespaces. The entries are the the
* {@link Namespace} resource locations.
* <p>
* This map in thread-safe as it uses {@link ConcurrentHashMap} to create a
* concurrent hash set.
* </p>
*/
private final Set<String> availableNamespaces = Collections
.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/**
* Defines the resource cache service dependency.
*/
private final CacheableResourceService resourceCache;
/**
* Defines the cache lookup service dependency.
*/
private final CacheLookupService cacheLookupService;
/**
* Defines the namespace indexer service dependency.
*/
private final NamespaceIndexerService namespaceIndexerService;
private boolean serviceDestroyed = false;
public DefaultNamespaceService(
final CacheableResourceService resourceCache,
final CacheLookupService cacheLookupService,
final NamespaceIndexerService namespaceIndexerService) {
this.resourceCache = resourceCache;
this.cacheLookupService = cacheLookupService;
this.namespaceIndexerService = namespaceIndexerService;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isOpen(String resourceLocation) {
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
return openNamespaces.containsKey(resourceLocation);
}
/**
* {@inheritDoc}
*/
@Override
public void compileNamespace(String resourceLocation)
throws ResourceDownloadError, IndexingFailure {
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
synchronized (resourceLocation) {
// compile
final String indexPath = doCompile(resourceLocation);
// close opened namespace index
closeNamespace(resourceLocation);
// reopen namespace index
openNamespace(resourceLocation, indexPath);
}
}
/**
* {@inheritDoc}
*/
@Override
public void verify(Parameter p) throws NamespaceSyntaxWarning,
IndexingFailure,
ResourceDownloadError {
if (nulls(p, p.getValue())) {
throw new InvalidArgument("p", p);
}
Namespace ns = p.getNamespace();
if (ns == null) {
return;
}
String resourceLocation = ns.getResourceLocation();
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
// check and open namespace for this parameter, if it has one
synchronized (resourceLocation) {
if (!isOpen(resourceLocation)) {
final String indexPath = doCompile(resourceLocation);
openNamespace(resourceLocation, indexPath);
}
}
doVerify(p);
}
/**
* {@inheritDoc}
*/
@Override
public void verify(Statement s) throws SymbolWarning, IndexingFailure,
ResourceDownloadError {
List<Parameter> parameters = s.getAllParameters();
// first iteration to open all namespaces
for (Parameter p : parameters) {
if (p.getNamespace() == null
|| p.getNamespace().getResourceLocation() == null) {
continue;
}
String resourceLocation = p.getNamespace().getResourceLocation();
synchronized (resourceLocation) {
if (!isOpen(resourceLocation)) {
final String indexPath = doCompile(resourceLocation);
openNamespace(resourceLocation, indexPath);
}
}
}
final List<NamespaceSyntaxWarning> exceptions =
new ArrayList<NamespaceSyntaxWarning>();
for (Parameter p : s.getAllParameters()) {
Namespace ns = p.getNamespace();
if (ns == null) {
continue;
}
try {
doVerify(p);
} catch (NamespaceSyntaxWarning w) {
exceptions.add(w);
}
}
if (!exceptions.isEmpty()) {
// TODO review name
final String name = s.toString();
final String fmt = INVALID_SYMBOLS;
final String msg = format(fmt, exceptions.size());
throw new SymbolWarning(name, msg, exceptions);
}
}
/**
* {@inheritDoc}
*/
@Override
public void verify(Document d) throws SymbolWarning, IndexingFailure,
ResourceDownloadError {
final List<NamespaceSyntaxWarning> exceptions =
new ArrayList<NamespaceSyntaxWarning>();
// open document namespaces if necessary
for (Namespace ns : d.getAllNamespaces()) {
String resourceLocation = ns.getResourceLocation();
synchronized (resourceLocation) {
if (!isOpen(resourceLocation)) {
final String indexPath = doCompile(resourceLocation);
openNamespace(resourceLocation, indexPath);
}
}
}
for (final Parameter p : d.getAllParameters()) {
Namespace ns = p.getNamespace();
if (ns == null) {
continue;
}
try {
doVerify(p);
} catch (NamespaceSyntaxWarning e) {
exceptions.add(e);
}
}
if (!exceptions.isEmpty()) {
final String name = d.getName();
final String fmt = INVALID_SYMBOLS;
final String msg = format(fmt, exceptions.size());
throw new SymbolWarning(name, msg, exceptions);
}
}
/**
* {@inheritDoc}
*/
@Override
public String lookup(Parameter p) throws NamespaceSyntaxWarning,
IndexingFailure {
if (nulls(p, p.getValue())) {
throw new InvalidArgument("parameter is null");
}
Namespace ns = p.getNamespace();
if (ns == null) {
return null;
}
String resourceLocation = ns.getResourceLocation();
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
// get opened namespace and lookup namespace parameter encoding
JDBMNamespaceLookup il = openNamespaces.get(ns.getResourceLocation());
if (il == null) {
final String fmt = "Namespace '%s' is not open.";
final String msg = String.format(fmt, resourceLocation);
throw new IndexingFailure(resourceLocation, msg);
}
String encoding = il.lookup(p.getValue());
if (encoding == null) {
String rl = ns.getResourceLocation();
String pref = ns.getPrefix();
throw new NamespaceSyntaxWarning(rl, pref, p.getValue());
}
return encoding;
}
/**
* {@inheritDoc}
*/
@Override
public Set<String> search(String resourceLocation, Pattern pattern)
throws IndexingFailure, ResourceDownloadError {
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
if (pattern == null) {
throw new InvalidArgument("pattern", pattern);
}
// check and open namespace for this resource location
synchronized (resourceLocation) {
if (!isOpen(resourceLocation)) {
String indexPath = doCompile(resourceLocation);
openNamespace(resourceLocation, indexPath);
}
}
return doSearch(resourceLocation, pattern);
}
/**
* Do compilation of the namespace resource.
*
* @param resourceLocation {@link String}, the resource location to compile
* @throws IndexingFailure Thrown if an error occurred while indexing
* @throws ResourceDownloadError Thrown if an error occurred downloading
* resource
*/
private String doCompile(String resourceLocation) throws IndexingFailure,
ResourceDownloadError {
final ResolvedResource resolved =
resourceCache.resolveResource(NAMESPACES, resourceLocation);
final File resourceCopy = resolved.getCacheResourceCopy();
namespaceIndexerService.indexNamespace(
resourceLocation, resolved.getCacheResourceCopy());
final String indexPath = asPath(resourceCopy.getParent(),
NAMESPACE_ROOT_DIRECTORY_NAME, NS_INDEX_FILE_NAME);
return indexPath;
}
/**
* Do a search on the values in namespace specified by the resource location
*
* @param resourceLocation resource location, e.g.,
* "http://resource.belframework.org/belframework/1.0/ns/chebi-ids.belns" ,
* can not be null
* @param pattern {@link Pattern}, can not be null
* @return {@link Set} of {@link String}s containing values that match
*/
private Set<String> doSearch(String resourceLocation, Pattern pattern) {
// get opened namespace
JDBMNamespaceLookup il = openNamespaces.get(resourceLocation);
Set<String> results = new HashSet<String>();
for (String value : il.getKeySet()) {
if (pattern.matcher(value).matches()) {
results.add(value);
}
}
return results;
}
/**
* Do namespace value verification against a resource location. This
* implementation assumes the namespace has been open prior to execution.
*
* @param p {@link Parameter}, the parameter to verify namespace value for
* which cannot be null and must have a non-null namespace and value
* @throws NamespaceSyntaxWarning Thrown if parameter's {@link Namespace} is
* not null and it does not contain the parameter's value
* @throws InvalidArgument Thrown if <tt>p</tt> argument is null, its value
* is null, or if its namespace's resource location is null
*/
private void doVerify(Parameter p) throws NamespaceSyntaxWarning {
if (p.getValue() == null) {
throw new InvalidArgument("parameter value is null");
}
Namespace ns = p.getNamespace();
String resourceLocation = ns.getResourceLocation();
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
// get opened namespace and lookup namespace parameter encoding
JDBMNamespaceLookup il = openNamespaces.get(ns.getResourceLocation());
if (il == null) {
throw new IllegalStateException("namespace index is not open.");
}
String encoding = il.lookup(p.getValue());
if (encoding == null) {
throw new NamespaceSyntaxWarning(ns.getResourceLocation(),
ns.getPrefix(),
p.getValue());
}
}
/**
* Determines if the {@link Namespace} resource location can be opened for
* lookups. To be opened
*
* @param resourceLocation
* @return
*/
private boolean canOpen(String resourceLocation) {
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
synchronized (resourceLocation) {
// find the resource in the cache
CachedResource cachedResource = cacheLookupService
.findInCache(NAMESPACES, resourceLocation);
if (cachedResource == null) {
return false;
}
String indexPath = asPath(
cachedResource.getLocalFile().getAbsolutePath(),
NS_INDEX_FILE_NAME);
JDBMNamespaceLookup il = new JDBMNamespaceLookup(indexPath);
try {
il.open();
il.close();
} catch (IOException e) {
return false;
}
return true;
}
}
private void openNamespace(final String resourceLocation,
final String indexPath) throws IndexingFailure {
if (resourceLocation == null) {
throw new InvalidArgument("resourceLocation", resourceLocation);
}
if (openNamespaces.containsKey(resourceLocation)) {
throw new IllegalStateException("namespace " + resourceLocation
+ " is already open.");
}
synchronized (resourceLocation) {
JDBMNamespaceLookup il = new JDBMNamespaceLookup(indexPath);
try {
il.open();
} catch (IOException e) {
final String fmt = "Failed to open namespace '%s'.";
final String msg = String.format(fmt, resourceLocation);
throw new IndexingFailure(resourceLocation, msg, e);
}
openNamespaces.put(resourceLocation, il);
}
}
private void closeNamespace(final String resourceLocation)
throws IndexingFailure {
synchronized (resourceLocation) {
JDBMNamespaceLookup jdbmlookup =
openNamespaces.get(resourceLocation);
if (jdbmlookup != null) {
try {
jdbmlookup.close();
openNamespaces.remove(resourceLocation);
} catch (IOException e) {
final String fmt = "Failed to close namespace '%s'.";
final String msg = String.format(fmt, resourceLocation);
throw new IndexingFailure(resourceLocation, msg, e);
}
}
}
}
private void closeNamespaces() throws IndexingFailure {
// close each JDBM btree index in turn
for (Map.Entry<String, JDBMNamespaceLookup> entry : openNamespaces
.entrySet()) {
closeNamespace(entry.getKey());
}
// clear out the open namespaces map
openNamespaces.clear();
}
/**
* {@inheritDoc}
*/
@Override
public void destroy() throws DestroyFailedException {
try {
closeNamespaces();
serviceDestroyed = true;
} catch (IndexingFailure e) {
throw new DestroyFailedException(
"Namespace service cannot be destroyed.");
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDestroyed() {
return serviceDestroyed;
}
public boolean isRunning() {
return running;
}
public void start() {
List<CachedResource> resources = cacheLookupService.getResources();
for (CachedResource resource : resources) {
if (NAMESPACES.equals(resource.getType())) {
String resourceLocation = resource.getRemoteLocation();
if (canOpen(resourceLocation)) {
availableNamespaces.add(resourceLocation);
}
}
}
running = true;
}
public void stop() {
}
}