com.oracle.tools.runtime.java.AbstractJavaApplication Maven / Gradle / Ivy
/*
* File: AbstractJavaApplication.java
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* The contents of this file are subject to the terms and conditions of
* the Common Development and Distribution License 1.0 (the "License").
*
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the License by consulting the LICENSE.txt file
* distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file LICENSE.txt.
*
* MODIFICATIONS:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*/
package com.oracle.tools.runtime.java;
import com.oracle.tools.Option;
import com.oracle.tools.deferred.Cached;
import com.oracle.tools.deferred.Deferred;
import com.oracle.tools.deferred.NeverAvailable;
import com.oracle.tools.deferred.PermanentlyUnavailableException;
import com.oracle.tools.deferred.jmx.DeferredJMXConnector;
import com.oracle.tools.deferred.jmx.DeferredMBeanAttribute;
import com.oracle.tools.deferred.jmx.DeferredMBeanInfo;
import com.oracle.tools.deferred.jmx.DeferredMBeanProxy;
import com.oracle.tools.runtime.AbstractApplication;
import com.oracle.tools.runtime.Application;
import com.oracle.tools.runtime.ApplicationListener;
import com.oracle.tools.runtime.Platform;
import com.oracle.tools.runtime.concurrent.RemoteCallable;
import com.oracle.tools.runtime.concurrent.RemoteRunnable;
import com.oracle.tools.runtime.concurrent.callable.RemoteMethodInvocation;
import com.oracle.tools.util.CompletionListener;
import com.oracle.tools.util.FutureCompletionListener;
import com.oracle.tools.util.ReflectionHelper;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import static com.oracle.tools.deferred.DeferredHelper.cached;
import static com.oracle.tools.deferred.DeferredHelper.ensured;
import static com.oracle.tools.deferred.DeferredHelper.within;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.management.MBeanInfo;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.remote.JMXConnector;
/**
* A {@link AbstractJavaApplication} is a base implementation of a {@link JavaApplication} that has
* a Console (with standard output, standard error and standard input streams).
*
* Copyright (c) 2011. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
*
* @author Brian Oliver
*/
public abstract class AbstractJavaApplication,
P extends JavaApplicationProcess, R extends JavaApplicationRuntime>
extends AbstractApplication implements FluentJavaApplication
{
/**
* The {@link Cached} representing the {@link JMXConnector}.
*/
protected Cached cachedJmxConnector;
/**
* Construct a {@link AbstractJavaApplication}.
*
* @param runtime the {@link JavaApplicationRuntime} for the {@link JavaApplication}
* @param listeners the {@link ApplicationListener}s
*/
public AbstractJavaApplication(R runtime,
Iterable> listeners)
{
super(runtime, listeners);
// ensure that the RMI server doesn't eagerly load JMX classes
System.setProperty("java.rmi.server.useCodebaseOnly", "true");
// establish our JMX connector
if (isJMXEnabled())
{
// define a DeferredResource representation of a JMXConnector
// for this application. we use a DeferredResource as the
// application may not have started, or be ready for JMX connections
String url = String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi",
getRMIServerHostName(),
getRemoteJMXPort());
// use a CachedResource as once the JMXConnector is established
// we don't want to create another JMXConnector for the application
cachedJmxConnector = cached(new DeferredJMXConnector(url));
}
else
{
cachedJmxConnector = cached(new NeverAvailable(JMXConnector.class));
}
}
/**
* Submits a {@link Callable} for remote execution by the
* {@link Application} and blocks waiting for the result.
*
* @param callable the {@link Callable} to execute
* @param the type of the result
*/
public T submit(RemoteCallable callable)
{
FutureCompletionListener future = new FutureCompletionListener();
submit(callable, future);
try
{
return future.get();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Failed to execute the Callable: " + callable, e);
}
}
@Override
public Properties getSystemProperties()
{
return runtime.getSystemProperties();
}
@Override
public String getSystemProperty(String name)
{
return getSystemProperties().getProperty(name);
}
@Override
public boolean isJMXEnabled()
{
return getSystemProperties().containsKey(SUN_MANAGEMENT_JMXREMOTE_PORT);
}
@Override
public int getRemoteJMXPort()
{
if (isJMXEnabled())
{
return Integer.parseInt(getSystemProperties().getProperty(SUN_MANAGEMENT_JMXREMOTE_PORT));
}
else
{
throw new UnsupportedOperationException("Application is not enabled for remote JMX management");
}
}
@Override
public String getRMIServerHostName()
{
String hostname = getSystemProperties().getProperty(JAVA_RMI_SERVER_HOSTNAME);
return hostname == null ? getPlatform().getAddress().getHostAddress() : hostname;
}
@Override
public Deferred getDeferredJMXConnector()
{
return cachedJmxConnector;
}
@Override
public Deferred getDeferredMBeanProxy(ObjectName objectName,
Class proxyClass)
{
return new Cached(new DeferredMBeanProxy(cachedJmxConnector, objectName, proxyClass));
}
@Override
public T getMBeanProxy(ObjectName objectName,
Class proxyClass)
{
return ensured(getDeferredMBeanProxy(objectName, proxyClass), within(getDefaultTimeout())).get();
}
@Override
public Deferred getDeferredMBeanInfo(ObjectName objectName)
{
return new DeferredMBeanInfo(cachedJmxConnector, objectName);
}
@Override
public MBeanInfo getMBeanInfo(ObjectName objectName)
{
return ensured(getDeferredMBeanInfo(objectName), within(getDefaultTimeout())).get();
}
@Override
public Deferred getDeferredMBeanAttribute(ObjectName objectName,
String attributeName,
Class attributeClass)
{
return new DeferredMBeanAttribute(cachedJmxConnector, objectName, attributeName, attributeClass);
}
@Override
public T getMBeanAttribute(ObjectName objectName,
String attributeName,
Class attributeClass)
{
return ensured(getDeferredMBeanAttribute(objectName, attributeName, attributeClass),
within(getDefaultTimeout())).get();
}
@Override
public Set queryMBeans(ObjectName name,
QueryExp query)
{
try
{
return ensured(getDeferredJMXConnector(),
within(getDefaultTimeout())).get().getMBeanServerConnection().queryMBeans(name, query);
}
catch (IOException e)
{
throw new PermanentlyUnavailableException(getDeferredJMXConnector(), e);
}
}
@Override
public void submit(RemoteCallable callable,
CompletionListener listener)
{
runtime.getApplicationProcess().submit(callable, listener);
}
@Override
public void submit(RemoteRunnable runnable) throws IllegalStateException
{
runtime.getApplicationProcess().submit(runnable);
}
@Override
public void close(Option... options)
{
// close the JMXConnector (if we've got one)
JMXConnector jmxConnector = cachedJmxConnector.release();
if (jmxConnector != null)
{
try
{
jmxConnector.close();
}
catch (IOException e)
{
// nothing to do here as we don't care
}
}
super.close(options);
}
@Override
public InetSocketAddress getRemoteDebugSocket()
{
int remoteDebuggingPort = runtime.getRemoteDebuggingPort();
if (remoteDebuggingPort <= 0)
{
return null;
}
Platform platform = getPlatform();
if (platform == null)
{
return null;
}
InetAddress address = platform.getAddress();
return new InetSocketAddress(address, remoteDebuggingPort);
}
@Override
public T getProxyFor(Class classToProxy,
RemoteCallable instanceProducer,
RemoteMethodInvocation.Interceptor interceptor)
{
return ReflectionHelper.createProxyOf(classToProxy, new ProxyMethodInterceptor(instanceProducer, interceptor));
}
/**
* The {@link MethodInterceptor} for remotely proxied classes.
*
* @param the type of the proxied class
*/
private class ProxyMethodInterceptor implements MethodInterceptor
{
/**
* The {@link RemoteCallable} that will provide the application instance
* on which proxy method calls should be invoked
*/
private RemoteCallable instanceProducer;
/**
* An optional {@link RemoteMethodInvocation.Interceptor} to
* intercept remote {@link Method} invocations.
*/
private RemoteMethodInvocation.Interceptor interceptor;
/**
* Constructs a {@link ProxyMethodInterceptor} with the ability to raise
* {@link UnsupportedOperationException}s for {@link Method}s that can't be implemented.
*
* @param instanceProducer the {@link RemoteCallable} that produces the instance on which
* proxied methods should be invoked
* @param interceptor the optional {@link RemoteMethodInvocation.Interceptor} to
* intercept remote {@link Method} invocations
*/
public ProxyMethodInterceptor(RemoteCallable instanceProducer,
RemoteMethodInvocation.Interceptor interceptor)
{
this.instanceProducer = instanceProducer;
this.interceptor = interceptor;
}
@Override
public Object intercept(Object self,
Method method,
Object[] args,
MethodProxy methodProxy) throws Throwable
{
if (interceptor != null)
{
interceptor.onBeforeRemoteInvocation(method, args);
}
FutureCompletionListener listener = new FutureCompletionListener();
AbstractJavaApplication.this.submit(new RemoteMethodInvocation(instanceProducer,
method.getName(),
args,
interceptor), listener);
try
{
Object result = listener.get();
if (interceptor != null)
{
result = interceptor.onAfterRemoteInvocation(method, args, result);
}
return result;
}
catch (Exception e)
{
if (interceptor == null)
{
throw e;
}
else
{
throw interceptor.onRemoteInvocationException(method, args, e);
}
}
}
}
}