com.oracle.bedrock.runtime.java.profiles.RemoteDebugging Maven / Gradle / Ivy
Show all versions of bedrock-runtime Show documentation
/*
* File: RemoteDebugging.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.bedrock.runtime.java.profiles;
import com.oracle.bedrock.Option;
import com.oracle.bedrock.OptionsByType;
import com.oracle.bedrock.annotations.Internal;
import com.oracle.bedrock.io.NetworkHelper;
import com.oracle.bedrock.lang.ExpressionEvaluator;
import com.oracle.bedrock.runtime.Application;
import com.oracle.bedrock.runtime.LocalPlatform;
import com.oracle.bedrock.runtime.MetaClass;
import com.oracle.bedrock.runtime.Platform;
import com.oracle.bedrock.runtime.Profile;
import com.oracle.bedrock.runtime.java.JavaApplication;
import com.oracle.bedrock.runtime.java.JavaVirtualMachine;
import com.oracle.bedrock.runtime.java.options.JvmOption;
import com.oracle.bedrock.runtime.java.options.WaitToStart;
import com.oracle.bedrock.runtime.network.AvailablePortIterator;
import com.oracle.bedrock.util.Capture;
import com.oracle.bedrock.util.PerpetualIterator;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
/**
* Defines a {@link Profile} to enable/disable Remote Java Debugging for {@link JavaApplication}s.
*
* Copyright (c) 2016. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
*
* @author Brian Oliver
*/
public class RemoteDebugging implements Profile, Option
{
/**
* Should remote debugging be enabled for a {@link JavaApplication}.
*/
private boolean enabled;
/**
* Should the {@link JavaApplication} be suspend when starting
* (to allow for connection from a debugger).
*/
private boolean startSuspended;
/**
* The {@link JavaApplication} {@link Behavior} for remote debugging.
*/
private Behavior behavior;
/**
* An optional {@link TransportAddress} to use for {@link RemoteDebugging}.
*
* If it's not provided (null), the {@link RemoteDebugging} profile will attempt to resolve
* one from the provided {@link OptionsByType} and if that's not available, it will a port
* decided by the underlying {@link Platform}.
*/
private TransportAddress transportAddress;
/**
* Defines how a {@link JavaApplication} will behave when remote-debugging
* is enabled.
*/
public enum Behavior
{
/**
* The {@link JavaApplication} will listen for connections from a debugger.
*/
LISTEN_FOR_DEBUGGER,
/**
* The {@link JavaApplication} will attempt to connect to a debugger.
*/
ATTACH_TO_DEBUGGER
}
/**
* Privately constructs a {@link RemoteDebugging} {@link Profile}.
*
* @param enabled is remote debugging enabled?
* @param startSuspended should the remotely debugged application start in suspended mode?
* @param behavior the {@link Behavior} of the application with respect to a debugger
* @param transportAddress the optional {@link TransportAddress}
*/
private RemoteDebugging(boolean enabled,
boolean startSuspended,
Behavior behavior,
TransportAddress transportAddress)
{
this.enabled = enabled;
this.startSuspended = startSuspended;
this.behavior = behavior;
this.transportAddress = transportAddress;
}
/**
* Obtains if {@link RemoteDebugging} is enabled.
*
* @return true
if {@link RemoteDebugging} is enabled, false
otherwise
*/
public boolean isEnabled()
{
return enabled;
}
/**
* Obtains if {@link RemoteDebugging} will start a Java Virtual Machine in suspended mode.
*
* @return true
if {@link RemoteDebugging} suspends a Java Virtual Machine on start,
* false
otherwise
*/
public boolean isStartSuspended()
{
return startSuspended;
}
/**
* Obtains the {@link RemoteDebugging} {@link Behavior}.
*
* @return the {@link Behavior}
*/
public Behavior getBehavior()
{
return behavior;
}
/**
* Obtains the {@link RemoteDebugging} {@link TransportAddress}.
*
* @return the {@link TransportAddress}
*/
public TransportAddress getTransportAddress()
{
return transportAddress;
}
@Override
public void onLaunching(Platform platform,
MetaClass metaClass,
OptionsByType optionsByType)
{
if (enabled)
{
// determine the TransportAddress to use
TransportAddress transportAddress = this.transportAddress == null
? optionsByType.get(TransportAddress.class) : this.transportAddress;
// create one if one hasn't been provided
if (transportAddress == null)
{
if (behavior == Behavior.LISTEN_FOR_DEBUGGER)
{
// when we don't have an address we use the platform provides
transportAddress = new TransportAddress(LocalPlatform.get().getAvailablePorts());
}
else
{
throw new IllegalStateException("Failed to specify a RemoteDebugging.TransportAddress option for attaching the debugger.");
}
}
// determine the transport address
String address;
if (transportAddress.getInetAddress() == null)
{
address = transportAddress.getPort().get().toString();
}
else
{
address = transportAddress.getInetAddress().getHostAddress() + ":" + transportAddress.getPort().get();
}
// determine if we're going to be in Server Mode
boolean isDebugServer = behavior == Behavior.LISTEN_FOR_DEBUGGER;
// construct the agent JvmOption
Agent agent = new Agent(String.format("-agentlib:jdwp=transport=dt_socket,server=%s,suspend=%s,address=%s",
(isDebugServer ? "y" : "n"),
(startSuspended ? "y" : "n"),
address),
transportAddress.getSocketAddress());
// add the agent
optionsByType.add(agent);
// disable waiting for the application to start if we're in suspend mode
if (startSuspended)
{
optionsByType.add(WaitToStart.disabled());
}
}
}
@Override
public void onLaunched(Platform platform,
Application application,
OptionsByType optionsByType)
{
}
@Override
public void onClosing(Platform platform,
Application application,
OptionsByType optionsByType)
{
}
/**
* Obtains a {@link RemoteDebugging} {@link Profile}, with debugging enabled and
* not suspended.
*
* @return a {@link RemoteDebugging} {@link Option}
*/
public static RemoteDebugging enabled()
{
return new RemoteDebugging(true, false, Behavior.LISTEN_FOR_DEBUGGER, null);
}
/**
* Obtains a {@link RemoteDebugging} {@link Profile}, with debugging disabled.
*
* @return a {@link RemoteDebugging} {@link Option}
*/
public static RemoteDebugging disabled()
{
return new RemoteDebugging(false, false, Behavior.LISTEN_FOR_DEBUGGER, null);
}
/**
* Obtains a {@link RemoteDebugging} {@link Profile}, with debugging enabled but
* with starting optionally suspended.
*
* @param startSuspended should {@link RemoteDebugging} start suspended
*
* @return a {@link RemoteDebugging} {@link Option}
*/
public RemoteDebugging startSuspended(boolean startSuspended)
{
return new RemoteDebugging(true, startSuspended, Behavior.LISTEN_FOR_DEBUGGER, null);
}
/**
* Obtains a {@link RemoteDebugging} profile that configures attaching to a debugger.
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging attach()
{
return new RemoteDebugging(enabled, startSuspended, Behavior.ATTACH_TO_DEBUGGER, null);
}
/**
* Obtains a {@link RemoteDebugging} profile that configures listening for a debugger.
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging listen()
{
return new RemoteDebugging(enabled, startSuspended, Behavior.LISTEN_FOR_DEBUGGER, null);
}
/**
* Obtains a {@link RemoteDebugging} profile that configures a specific {@link TransportAddress}.
*
* @param transportAddress the {@link TransportAddress}
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging at(TransportAddress transportAddress)
{
return new RemoteDebugging(enabled, startSuspended, behavior, transportAddress);
}
/**
* Obtains a {@link RemoteDebugging} profile that configures a specific {@link TransportAddress}
* using an {@link AvailablePortIterator}
*
* @param ports the {@link AvailablePortIterator}
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging at(AvailablePortIterator ports)
{
return new RemoteDebugging(enabled, startSuspended, behavior, new TransportAddress(ports));
}
/**
* Obtains a {@link RemoteDebugging} profile that configures a specific {@link TransportAddress}
* at a designated port on the {@link Platform}.
*
* @param port the port
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging at(Capture port)
{
return new RemoteDebugging(enabled, startSuspended, behavior, new TransportAddress(port));
}
/**
* Obtains a {@link RemoteDebugging} profile that configures a specific {@link TransportAddress}
* at a designated port and address.
*
* @param address the {@link InetAddress}
* @param port the port
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging at(InetAddress address,
Capture port)
{
return new RemoteDebugging(enabled, startSuspended, behavior, new TransportAddress(address, port));
}
/**
* Obtains a {@link RemoteDebugging} profile that configures a specific {@link TransportAddress}
* at a designated port and address.
*
* @param address the {@link InetAddress}
* @param port the port
*
* @return a new {@link RemoteDebugging} profile
*/
public RemoteDebugging at(InetAddress address,
int port)
{
return new RemoteDebugging(enabled, startSuspended, behavior, new TransportAddress(address, port));
}
/**
* Obtains a {@link RemoteDebugging} {@link Profile}, auto-detecting if it should be
* enabled based on the Java process in which the thread is executing.
*
* @return a {@link RemoteDebugging} {@link Option}
*/
@OptionsByType.Default
public static RemoteDebugging autoDetect()
{
return new RemoteDebugging(JavaVirtualMachine.get().shouldEnableRemoteDebugging(),
false,
Behavior.LISTEN_FOR_DEBUGGER,
null);
}
/**
* The internal {@link RemoteDebugging} Agent {@link JvmOption}.
*
* This is {@link JvmOption} is produced by the {@link RemoteDebugging} profile
* when a {@link JavaApplication} is launched to setup remote debugging.
*/
@Internal
public static class Agent implements JvmOption
{
/**
* The {@link RemoteDebugging.Agent} {@link JvmOption} configuration.
*/
private String configuration;
/**
* The transport {@link InetSocketAddress} used for remote debugging.
*/
private InetSocketAddress address;
/**
* Constructs a {@link RemoteDebugging.Agent}.
*
* @param configuration the configuration of the agent
* @param address the transport {@link InetSocketAddress} for remote debugging
*/
public Agent(String configuration,
InetSocketAddress address)
{
this.configuration = configuration;
this.address = address;
}
/**
* Obtains the transport {@link InetSocketAddress} for the {@link Agent}.
*
* @return the {@link InetSocketAddress}
*/
public InetSocketAddress getSocketAddress()
{
return address;
}
@Override
public Iterable resolve(OptionsByType optionsByType)
{
ExpressionEvaluator evaluator = new ExpressionEvaluator(optionsByType);
return Collections.singletonList(evaluator.evaluate(configuration, String.class));
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (!(o instanceof Agent))
{
return false;
}
Agent agent = (Agent) o;
return configuration != null ? configuration.equals(agent.configuration) : agent.configuration == null;
}
@Override
public int hashCode()
{
return configuration != null ? configuration.hashCode() : 0;
}
@Override
public String toString()
{
return "Agent{" + configuration + '}';
}
}
/**
* The transport address for {@link RemoteDebugging}.
*/
public static class TransportAddress implements Option
{
/**
* The optional address for attaching/connecting/listening for the Remote Debugger.
* (when null this implies a local address)
*/
private InetAddress address;
/**
* The port for attaching/connecting/listening for the Remote Debugger.
*/
private Capture port;
/**
* Constructs a local {@link TransportAddress}, choosing a port from the
* {@link AvailablePortIterator}.
*
* @param ports the available ports
*/
public TransportAddress(AvailablePortIterator ports)
{
// NOTE: Currently Java Debugging only supports IPv4 on the server-side so we must
// filter the AvailablePortIterator addresses into those that are IPv4
this(NetworkHelper.getInetAddresses(ports.getInetAddresses(), NetworkHelper.IPv4_ADDRESS).iterator().next(),
new Capture<>(ports));
}
/**
* Constructs a local {@link TransportAddress} for the specified port.
*
* @param port the port
*/
public TransportAddress(Capture port)
{
this(null, port);
}
/**
* Constructs a local {@link TransportAddress} for the specified port.
*
* @param port the port
*/
public TransportAddress(int port)
{
this(null, new Capture<>(new PerpetualIterator<>(port)));
}
/**
* Constructs a {@link TransportAddress} with the specified port.
*
* @param address the address
* @param port the port
*/
public TransportAddress(InetAddress address,
Capture port)
{
if (port == null)
{
throw new NullPointerException("The port for an address can't be null");
}
else
{
this.address = address;
this.port = port;
}
}
/**
* Constructs a {@link TransportAddress} with the specified port.
*
* @param address the address
* @param port the port
*/
public TransportAddress(InetAddress address,
int port)
{
this(address, new Capture<>(new PerpetualIterator<>(port)));
}
/**
* Obtains the address for the {@link RemoteDebugging} transport (which may be null).
*
* @return the {@link InetAddress}
*/
public InetAddress getInetAddress()
{
return address;
}
/**
* Obtains the {@link InetSocketAddress} for the {@link RemoteDebugging} transport (which may be null).
*
* @return an {@link InetSocketAddress}
*/
public InetSocketAddress getSocketAddress()
{
return getInetAddress() == null ? null : new InetSocketAddress(getInetAddress(), port.get());
}
/**
* Obtains the port for the {@link RemoteDebugging} transport.
*
* @return the port
*/
public Capture getPort()
{
return port;
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (!(o instanceof TransportAddress))
{
return false;
}
TransportAddress other = (TransportAddress) o;
if (address != null ? !address.equals(other.address) : other.address != null)
{
return false;
}
return port.equals(other.port);
}
@Override
public int hashCode()
{
int result = address != null ? address.hashCode() : 0;
result = 31 * result + port.hashCode();
return result;
}
}
}