blob: 2f5d40a41eafdea393fbc3383161fcd8f1668a7e [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.v23;
import com.google.common.io.ByteStreams;
import com.google.common.io.Resources;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import io.v.impl.google.rt.VRuntimeImpl;
import io.v.v23.context.VContext;
import io.v.v23.discovery.Discovery;
import io.v.v23.namespace.Namespace;
import io.v.v23.rpc.Client;
import io.v.v23.rpc.Dispatcher;
import io.v.v23.rpc.ListenSpec;
import io.v.v23.rpc.Server;
import io.v.v23.security.Authorizer;
import io.v.v23.security.CaveatRegistry;
import io.v.v23.security.ConstCaveatValidator;
import io.v.v23.security.Constants;
import io.v.v23.security.ExpiryCaveatValidator;
import io.v.v23.security.MethodCaveatValidator;
import io.v.v23.security.PublicKeyThirdPartyCaveatValidator;
import io.v.v23.security.VPrincipal;
import io.v.v23.verror.VException;
/**
* The local environment allowing clients and servers to communicate with one another. The expected
* usage pattern of this class goes something like this:
* <p><blockquote><pre>
* VContext ctx = V.init(opts);
* ...
* ctx = V.withNewServer(ctx, "server", obj, null);
* ...
* Client c = V.getClient(ctx);
* ...
* </pre></blockquote><p>
*/
public class V {
private static native void nativeInitGlobalShared() throws VException;
private static native void nativeInitGlobalJava(Options opts) throws VException;
private static volatile VRuntime runtime;
private static volatile VContext globalContext;
private static boolean isDarwin() {
return System.getProperty("os.name").toLowerCase().contains("os x");
}
private static boolean isLinux() {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
// Initializes the Vanadium global state, i.e., state that needs to be cleaned up only
// before the process is about to terminate. This method is shared between Java and Android
// implementations.
protected static VContext initGlobalShared(Options opts) {
List<Throwable> errors = new ArrayList<Throwable>();
try {
// First, attempt to find the library in java.library.path.
System.loadLibrary("v23");
} catch (UnsatisfiedLinkError ule) {
// Thrown if the library does not exist. In this case, try to find it in our classpath.
errors.add(new RuntimeException("loadLibrary attempt failed", ule));
try {
URL resource = null;
File file = null;
if (isLinux()) {
resource = Resources.getResource("libv23.so");
file = File.createTempFile("libv23-", ".so");
} else if (isDarwin()) {
resource = Resources.getResource("libv23.dylib");
file = File.createTempFile("libv23-", ".dylib");
} else {
String os = System.getProperty("os.name");
errors.add(new RuntimeException("unsupported OS: " + os));
throw new RuntimeException("Unsupported OS: " + os, new VLoaderException(errors));
}
file.deleteOnExit();
ByteStreams.copy(resource.openStream(), new FileOutputStream(file));
System.load(file.getAbsolutePath());
} catch (IllegalArgumentException iae) {
errors.add(new RuntimeException("couldn't locate libv23.so on the classpath", iae));
throw new RuntimeException("Could not load v23 native library", new VLoaderException(errors));
} catch (IOException e) {
errors.add(new RuntimeException("error while reading libv23.so from the classpath", e));
throw new RuntimeException("Could not load v23 native library", new VLoaderException(errors));
} catch (UnsatisfiedLinkError e) {
errors.add(new RuntimeException("error while reading libv23.so from the classpath", e));
throw new RuntimeException("Could not load v23 native library", new VLoaderException(errors));
}
}
try {
nativeInitGlobalShared();
} catch (VException e) {
throw new RuntimeException("Could not initialize v23 native library", e);
}
try {
CaveatRegistry.register(
io.v.v23.security.Constants.CONST_CAVEAT,
ConstCaveatValidator.INSTANCE);
CaveatRegistry.register(
io.v.v23.security.Constants.EXPIRY_CAVEAT,
ExpiryCaveatValidator.INSTANCE);
CaveatRegistry.register(io.v.v23.security.Constants.METHOD_CAVEAT,
MethodCaveatValidator.INSTANCE);
CaveatRegistry.register(Constants.PUBLIC_KEY_THIRD_PARTY_CAVEAT,
PublicKeyThirdPartyCaveatValidator.INSTANCE);
} catch (VException e) {
throw new RuntimeException("Couldn't register caveat validators", e);
}
// See if a runtime was provided as an option.
if (opts.get(OptionDefs.RUNTIME) != null) {
runtime = opts.get(OptionDefs.RUNTIME, VRuntime.class);
} else {
// Use the default runtime implementation.
try {
runtime = VRuntimeImpl.create(opts);
} catch (VException e) {
throw new RuntimeException("Couldn't initialize Google Vanadium Runtime", e);
}
}
VContext ctx = runtime.getContext();
if (!ctx.isCancelable()) {
throw new RuntimeException("Context returned by the runtime must be cancelable");
}
return ctx;
}
// Initializes the Vanadium Java-specific global state.
private static VContext initGlobalJava(VContext ctx, Options opts) {
try {
nativeInitGlobalJava(opts);
ctx = V.withExecutor(ctx, Executors.newCachedThreadPool());
return ctx;
} catch (VException e) {
throw new RuntimeException("Couldn't initialize Java", e);
}
}
// Initializes the Vanadium global state
private static VContext initGlobal(Options opts) {
if (globalContext != null) {
return globalContext;
}
synchronized (V.class) {
if (globalContext != null) {
return globalContext;
}
if (opts == null) opts = new Options();
VContext ctx = initGlobalShared(opts);
ctx = initGlobalJava(ctx, opts);
// Set the VException component name to this binary name.
ctx = VException.contextWithComponentName(ctx, System.getProperty("program.name", ""));
globalContext = ctx;
return ctx;
}
}
/**
* Initializes the Vanadium environment, returning the base context. This method may be
* called multiple times: in each invocation, a different base context will be returned.
* <p>
* {@link VContext#cancel() Canceling} the returned context will release all the Vanadium
* resources and shut down all services associated with the context. Forgetting to cancel
* the context will therefore result in memory leaks of those resources.
* <p>
* A caller may pass the following option that specifies the runtime implementation to be used:
* <p><ul>
* <li>{@link OptionDefs#RUNTIME}</li>
* </ul><p>
* If this option isn't provided, the default runtime implementation is used, which supports
* the following options:
* <p><ul>
* <li>(CURRENTLY NO OPTIONS ARE MANDATED)</li>
* </ul></p>
*
* @param opts options
* @return base context
*/
private static synchronized VContext init(Options opts) {
VContext ctx = initGlobal(opts);
return ctx.withCancel();
}
/**
* Initializes the Vanadium environment without options. See {@link #init(Options)} for more
* information.
*
* @return base context
*/
public static VContext init() {
return V.init(null);
}
/**
* Creates a new client instance and attaches it to a new context (which is derived from the
* provided context).
*
* @param ctx current context
* @return child context to which the new client is attached
* @throws VException if a new client cannot be created
*/
public static VContext withNewClient(VContext ctx) throws VException {
return withNewClient(ctx, null);
}
/**
* Creates a new client instance with the provided options and attaches it to a new context
* (which is derived from the provided context).
* <p></p>
* A particular runtime implementation chooses which options to support, but at the minimum must
* handle the following options:
* <p><ul>
* <li>(CURRENTLY NO OPTIONS ARE MANDATED)</li>
* </ul>
*
* @param ctx current context
* @param opts client options
* @return child context to which the new client is attached
* @throws VException if a new client cannot be created
*/
public static VContext withNewClient(VContext ctx, Options opts) throws VException {
if (opts == null) opts = new Options();
return getRuntime(ctx).withNewClient(ctx, opts);
}
/**
* Returns the client attached to the given context, or {@code null} if no client is attached.
* <p>
* If the passed-in context is derived from the context returned by {@link #init}, the returned
* client will never be {@code null}.
*/
public static Client getClient(VContext ctx) {
return getRuntime(ctx).getClient(ctx);
}
/**
* Creates a new {@link Server} instance to serve a service object and attaches
* it to a new context (which is derived from the provided context).
*
* The server will listen for network connections as specified by the {@link ListenSpec}
* attached to the context. Depending on your runtime, 'roaming' support may be enabled.
* In this mode the server will adapt to changes in the network configuration
* and re-publish the current set of endpoints to the mount table accordingly.
* <p>
* This call associates object with name by publishing the address of this server with the
* mount table under the supplied name and using the given authorizer to authorize access to it.
* RPCs invoked on the supplied name will be delivered to methods implemented by the supplied
* object.
* <p>
* Reflection is used to match requests to the object's method set. As a special-case, if the
* object implements the {@link io.v.v23.rpc.Invoker} interface, the invoker is used to invoke
* methods directly, without reflection.
* <p>
* If name is an empty string, no attempt will made to publish that name to a mount table.
* <p>
* If the passed-in authorizer is {@code null}, the default authorizer will be used.
* (The default authorizer uses the blessing chain derivation to determine if the client is
* authorized to access the object's methods.)
*
* @param ctx current context
* @param name name under which the supplied object should be published,
* or the empty string if the object should not be published
* @param object object to be published under the given name
* @param authorizer authorizer that will control access to objects methods
* @return a child context to which the new server is attached
* @throws VException if a new server cannot be created
*/
public static VContext withNewServer(VContext ctx, String name, Object object,
Authorizer authorizer) throws VException {
return withNewServer(ctx, name, object, authorizer, null);
}
/**
* Creates a new {@link Server} instance to serve a service object and attaches
* it to a new context (which is derived from the provided context).
*
* Same as {@link #withNewServer(VContext, String, Object, Authorizer)}
* but accepts implementation-specific server-creation options.
* <p>
* A particular runtime implementation chooses which options to support, but at the minimum it
* must handle the following options:
* <p><ul>
* <li>(CURRENTLY NO OPTIONS ARE MANDATED)</li>
* </ul>
*
* @param ctx current context
* @param name name under which the supplied object should be published,
* or the empty string if the object should not be published
* @param object object to be published under the given name
* @param authorizer authorizer that will control access to objects methods
* @param opts server options
* @return a child context to which the new server is attached
* @throws VException if a new server cannot be created
*/
public static VContext withNewServer(VContext ctx, String name, Object object,
Authorizer authorizer, Options opts) throws VException {
if (opts == null) {
opts = new Options();
}
return getRuntime(ctx).withNewServer(ctx, name, object, authorizer, opts);
}
/**
* Creates a new {@link Server} instance to serve a dispatcher and attaches
* it to a new context (which is derived from the provided context).
*
* The server will listen for network connections as specified by the {@link ListenSpec}
* attached to the context. Depending on your runtime, 'roaming' support may be enabled.
* In this mode the server will adapt to changes in the network configuration
* and re-publish the current set of endpoints to the mount table accordingly.
* <p>
* Associates dispatcher with the portion of the mount table's name space for which
* {@code name} is a prefix, by publishing the address of this dispatcher with the mount
* table under the supplied name.
* <p>
* RPCs invoked on the supplied name will be delivered to the supplied {@link Dispatcher}'s
* {@link Dispatcher#lookup lookup} method which will in turn return the object and
* {@link Authorizer} used to serve the actual RPC call.
* <p>
* If name is an empty string, no attempt will made to publish that name to a mount table.
*
* @param ctx current context
* @param name name under which the supplied object should be published,
* or the empty string if the object should not be published
* @param dispatcher dispatcher to be published under the given name
* @return a child context to which the new server is attached
* @throws VException if a new server cannot be created
*/
public static VContext withNewServer(VContext ctx, String name,
Dispatcher dispatcher) throws VException {
return withNewServer(ctx, name, dispatcher, (Options) null);
}
/**
* Creates a new {@link Server} instance to serve a dispatcher and attaches
* it to a new context (which is derived from the provided context).
*
* Same as {@link #withNewServer(VContext,String,Dispatcher)} but accepts
* implementation-specific server-creation options.
* <p>
* A particular runtime implementation chooses which options to support,
* but at the minimum it must handle the following options:
* <p><ul>
* <li>(CURRENTLY NO OPTIONS ARE MANDATED)</li>
* </ul>
*
* @param ctx current context
* @param name name under which the supplied object should be published,
* or the empty string if the object should not be published
* @param dispatcher dispatcher to be published under the given name
* @param opts server options
* @return a child context to which the new server is attached
* @throws VException if a new server cannot be created
*/
public static VContext withNewServer(VContext ctx, String name, Dispatcher dispatcher,
Options opts) throws VException {
if (opts == null) {
opts = new Options();
}
return getRuntime(ctx).withNewServer(ctx, name, dispatcher, opts);
}
/**
* Returns the server attached to the given context, or {@code null} if no server is attached.
*/
public static Server getServer(VContext ctx) {
return getRuntime(ctx).getServer(ctx);
}
/**
* Attaches the given principal to a new context (which is derived from the provided context).
*
* @param ctx current context
* @param principal principal to be attached
* @return child context to which the principal is attached
* @throws VException if the principal couldn't be attached
*/
public static VContext withPrincipal(VContext ctx, VPrincipal principal) throws VException {
return getRuntime(ctx).withPrincipal(ctx, principal);
}
/**
* Returns the principal attached to the given context or {@code null} if no principal is
* attached.
* <p>
* If the passed-in context is derived from the context returned by {@link #init}, the returned
* principal will never be {@code null}.
*/
public static VPrincipal getPrincipal(VContext ctx) {
return getRuntime(ctx).getPrincipal(ctx);
}
/**
* Creates a new namespace instance and attaches it to a new context (which is derived from the
* given context).
*
* @param ctx current context
* @param roots roots of the namespace
* @return child context to which the principal is attached
* @throws VException if the namespace couldn't be created
*/
public static VContext withNewNamespace(VContext ctx, String... roots) throws VException {
return getRuntime(ctx).withNewNamespace(ctx, roots);
}
/**
* Returns the namespace attached to the given context, or {@code null} if no namespace is
* attached.
* <p>
* If the passed-in context is derived from the context returned by {@link #init}, the returned
* namespace will never be {@code null}.
*/
public static Namespace getNamespace(VContext ctx) {
return getRuntime(ctx).getNamespace(ctx);
}
/**
* Attaches the given {@link ListenSpec} to a new context (which is derived from the given
* context).
*
* @param ctx current context
* @param spec the {@link ListenSpec} to attach
* @return child context to which the {@link ListenSpec} is attached.
*/
public static VContext withListenSpec(VContext ctx, ListenSpec spec) throws VException {
return getRuntime(ctx).withListenSpec(ctx, spec);
}
/**
* Returns the {@link ListenSpec} attached to the given context, or {@code null} if no spec
* is attached.
* <p>
* If the passed-in context is derived from the context returned by {@link #init}, the returned
* spec will never be {@code null}.
*/
public static ListenSpec getListenSpec(VContext ctx) {
return getRuntime(ctx).getListenSpec(ctx);
}
/**
* Returns a new {@link Discovery} instance.
*
* @param ctx current context
* @throws VException if a new discovery instance cannot be created
*/
public static Discovery newDiscovery(VContext ctx) throws VException {
return getRuntime(ctx).newDiscovery(ctx);
}
/**
* Attaches the given {@link Executor} to a new context (which is derived from the given
* context). This executor will be used for all callbacks into the user code.
* <p>
* If this method is never invoked, a default executor for the platform will be used.
*/
public static VContext withExecutor(VContext ctx, Executor executor) {
return ctx.withValue(new ExecutorKey(), executor);
}
/**
* Returns the {@link Executor} attached to the given context, or {@code null} if no
* {@link Executor} is attached.
* <p>
* If the passed-in context is derived from the context returned by {@link #init}, the returned
* instance will never be {@code null}.
*/
public static Executor getExecutor(VContext ctx) {
return (Executor) ctx.value(new ExecutorKey());
}
private static VRuntime getRuntime(VContext ctx) {
if (runtime == null) {
throw new RuntimeException("Vanadium runtime is null: did you call V.init()?");
}
return runtime;
}
private static class ExecutorKey {
@Override
public int hashCode() {
return 0;
}
}
protected V() {}
}