| // 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() {} |
| } |