/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.persistit;
import java.io.IOException;
import java.net.InetAddress;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.management.ObjectName;
import com.persistit.encoding.CoderContext;
import com.persistit.encoding.ValueCoder;
import com.persistit.exception.PersistitException;
import com.persistit.exception.TreeNotFoundException;
/**
* <p>
* Exposes information about the Persistit environment. With this public API,
* embedding applications can query performance metrics and resources within
* Persistit that are not exposed by the normal access methods. For example,
* this class provides methods that enumerate the volumes currently mounted, the
* size and effectiveness of buffer pool components, and many other items that
* may be useful in tuning and managing Persistit.
* </p>
* <p>
* All information returned by methods of this class represent a snapshot of
* system state. Data structures such as
* {@link com.persistit.Management.BufferPoolInfo} represent system state at the
* time of that snapshot and are not kept updated as over time. Management
* applications that display system state continuously should periodically call
* methods of this class to get updated values.
* </p>
*
* @version 1.0
*/
class ManagementImpl implements Management {
private final static long MAX_STALE = 100;
// Static because you can only call LocateRegistry.createRegistry() once
// on a port.
private static int _localRegistryPort = -1;
private long _taskIdCounter;
private transient Persistit _persistit;
private transient DisplayFilter _displayFilter;
private boolean _registered = false;
private String _registeredHostName;
private final HashMap<Long, Task> _tasks = new HashMap<Long, Task>();
private final TransactionInfo _transactionInfoCache = new TransactionInfo();
public ManagementImpl(final Persistit persistit) {
_persistit = persistit;
_displayFilter = new DisplayFilter() {
@Override
public String toKeyDisplayString(final Exchange exchange) {
return exchange.getKey().toString();
}
@Override
public String toValueDisplayString(final Exchange exchange) {
return exchange.getValue().toString();
}
};
}
/**
* Indicates whether Persistit is currently in the initialized state.
*
* @return The state
*/
@Override
public boolean isInitialized() {
return _persistit.isInitialized();
}
/**
* Returns the version name of the current Peristit instance.
*
* @return the version name
*/
@Override
public String getVersion() {
return Persistit.version();
}
/**
* Returns the copyright notice for the current Persistit instance.
*
* @return the copyright notice
*/
@Override
public String getCopyright() {
return Persistit.copyright();
}
/**
* Returns the system time at which Persistit was initialized.
*
* @return start time, in milliseconds since January 1, 1970 00:00:00 GMT.
*/
@Override
public long getStartTime() {
return _persistit.startTime();
}
/**
* Returns the elapsed time since startup in milliseconds
*
* @return elapsed time in milliseconds
*/
@Override
public long getElapsedTime() {
return _persistit.elapsedTime();
}
@Override
public int getRmiPort() {
return _localRegistryPort;
}
@Override
public long getCommittedTransactionCount() {
return getTransactionInfo().getCommitCount();
}
@Override
public long getRollbackCount() {
return getTransactionInfo().getRollbackCount();
}
@Override
public VolumeInfo[] getVolumes() throws RemoteException {
return getVolumeInfoArray();
}
@Override
public Map<ObjectName, Object> getMXBeans() {
return _persistit.getMXBeans();
}
/**
* Indicates whether Persistit will suspend its shutdown activities on
* invocation of {@link #close}. This flag is intended for use by management
* tools that need to keep Persistit open even when the application has
* requested it to close so that the final state of the Persistit
* environment can be examined.
*
* @return <code>true</code> if Persistit will wait when attempting to
* close; <code>false</code> if the <code>close</code> operation
* will not be suspended.
*/
@Override
public boolean isShutdownSuspended() {
return _persistit.isShutdownSuspended();
}
/**
* Determines whether Persistit will suspend its shutdown activities on
* invocation of {@link #close}. This flag is intended for use by management
* tools that need to keep Persistit open even when the application has
* requested it to close so that the final state of the Persistit
* environment can be examined.
*
* @param enabled
* <code>true</code> to specify that Persistit will wait when
* attempting to close; otherwise <code>false</code>.
*/
@Override
public void setShutdownSuspended(final boolean suspended) {
_persistit.setShutdownSuspended(suspended);
}
/**
* Indicates whether Persistit is suspending all update operations. When
* enabled, Persistit will indefinitely delay each Thread that attempts to
* perform an update operation.
*
* @return <code>true</code> if Persistit will suspend any attempt to update
* a <code>Volume</code>; otherwise <code>false</code>.
* @throws RemoteException
*/
@Override
public boolean isUpdateSuspended() {
return _persistit.isUpdateSuspended();
}
/**
* Controls whether Persistit will suspend all update operations. When
* enabled, Persistit will delay each Thread that attempts to perform an
* update operation indefinitely.
*
* @param suspended
* @throws RemoteException
*/
@Override
public void setUpdateSuspended(final boolean suspended) {
_persistit.setUpdateSuspended(suspended);
}
/**
*
*/
public void setIoLogFile(final String path) throws RemoteException {
try {
if (path == null || path.isEmpty()) {
_persistit.getIOMeter().setLogFile(null);
} else {
_persistit.getIOMeter().setLogFile(path);
}
} catch (final IOException e) {
throw new WrappedRemoteException(e);
}
}
/**
* Controls whether Persistit will suspend the thread that copies pages from
* the journal back to their respective Volumes. This flag is used by tools
* that provide on-line backup.
*
* @param suspended
* <code>true</code> to specify that Persistit will suspend
* journal copying; otherwise <code>false</code>.
*/
@Override
public void setAppendOnly(final boolean suspended) {
_persistit.getJournalManager().setAppendOnly(suspended);
}
/**
* Controls whether Persistit copies page from the journal back to their
* volumes as fast as possible. Copying consumes disk I/O operations, so
* normally the copier thread pauses between copy operations to avoid
* saturating the disk. Once all pages have been copied, the fast copying
* flag is automatically turned off.
*
* @param fast
* <code>true</code> to copy pages at maximum speed.
* @throws RemoteException
*/
@Override
public void setJournalCopyingFast(final boolean fast) throws RemoteException {
_persistit.getJournalManager().setCopyingFast(fast);
}
/**
* @return the name of the current default <code>CommitPolicy<code>
*/
@Override
public String getDefaultCommitPolicy() {
return _persistit.getDefaultTransactionCommitPolicy().toString();
}
/**
* Modify the current default <code>CommitPolicy</code>. The policy name
* must be one of "hard", "group" or "commit".
*
* @param policyName
* name of the <code>CommitPolicy</code> to set.
*/
@Override
public void setDefaultCommitPolicy(final String policyName) {
_persistit.setDefaultTransactionCommitPolicy(policyName);
}
/**
* Attempts to close Persistit by invoking {@link Persistit#close}.
*
* @return <code>true</code> if the attempt to close Persistit was
* successful; otherwise <code>false</code>
* @throws RemoteException
*/
@Override
public boolean close() throws RemoteException {
try {
_persistit.close();
return true;
} catch (final PersistitException e) {
throw new WrappedRemoteException(e);
}
}
/**
* Attempts to flush and force all dirty data in Persistit by invoking
* {@link Persistit#flush} and {@link Persistit#force}.
*
* @throws RemoteException
*/
@Override
public void flushAndForce() throws RemoteException {
try {
_persistit.flush();
_persistit.force();
} catch (final PersistitException e) {
throw new WrappedRemoteException(e);
}
}
/**
* Returns an array containing a <code>BufferPoolInfo</code> element for
* each buffer pool. If Persistit is not initialized then this method
* returns an empty array.
*
* @return The array
*/
/**
* Returns an array containing a <code>BufferPoolInfo</code> element for
* each buffer pool. If Persistit is not initialized then this method
* returns an empty array.
*
* @return The array
*/
@Override
public BufferPoolInfo[] getBufferPoolInfoArray() {
final HashMap<Integer, BufferPool> bufferPoolTable = _persistit.getBufferPoolHashMap();
final int size = bufferPoolTable.size();
final BufferPoolInfo[] result = new BufferPoolInfo[size];
int index = 0;
for (int bufferSize = Buffer.MIN_BUFFER_SIZE; bufferSize <= Buffer.MAX_BUFFER_SIZE; bufferSize *= 2) {
final BufferPool pool = bufferPoolTable.get(new Integer(bufferSize));
if (pool != null && index < size) {
final BufferPoolInfo info = new BufferPoolInfo();
pool.populateBufferPoolInfo(info);
result[index++] = info;
}
}
return result;
}
@Override
public JournalInfo getJournalInfo() {
final JournalInfo info = new JournalInfo();
_persistit.getJournalManager().populateJournalInfo(info);
return info;
}
@Override
public RecoveryInfo getRecoveryInfo() {
final RecoveryInfo info = new RecoveryInfo();
_persistit.getRecoveryManager().populateRecoveryInfo(info);
return info;
}
@Override
public TransactionInfo getTransactionInfo() {
final TransactionInfo info = _transactionInfoCache;
if (System.currentTimeMillis() - info.getAcquisitionTime() > MAX_STALE) {
final List<Transaction> transactions = new ArrayList<Transaction>();
synchronized (info) {
info.commitCount = 0;
info.rollbackCount = 0;
info.rollbackSinceCommitCount = 0;
_persistit.populateTransactionList(transactions);
for (final Transaction txn : transactions) {
info.commitCount += txn.getCommittedTransactionCount();
info.rollbackCount += txn.getRolledBackTransactionCount();
info.rollbackSinceCommitCount += txn.getRolledBackSinceLastCommitCount();
}
info.updateAcquisitonTime();
}
}
return info;
}
@Override
public String transactionReport(final int max) {
return _persistit.transactionReport(max);
}
@Override
public LogicalRecord[] getLogicalRecordArray(final String volumeName, final String treeName,
final String keyFilterString, final KeyState fromKey, final Key.Direction direction, final int maxCount,
final int maxValueBytes, final boolean decodeStrings) throws RemoteException {
LogicalRecord[] records = new LogicalRecord[maxCount];
int count = 0;
final boolean forward = direction == Key.GT || direction == Key.GTEQ;
Exchange exchange = null;
try {
if (treeName.equals(VolumeStructure.DIRECTORY_TREE_NAME)) {
exchange = _persistit.getVolume(volumeName).getStructure().directoryExchange();
} else {
exchange = _persistit.getExchange(volumeName, treeName, false);
}
exchange.ignoreMVCCFetch(true);
KeyFilter filter = null;
if (keyFilterString != null && keyFilterString.length() > 0) {
filter = new KeyFilter(keyFilterString);
}
fromKey.copyTo(exchange.getKey());
for (; count < maxCount; count++) {
if (!exchange.traverse(direction, filter, maxValueBytes)) {
break;
} else {
final LogicalRecord record = new LogicalRecord();
record._key = new KeyState(exchange.getKey());
record._value = new ValueState(exchange.getValue(), maxValueBytes);
if (decodeStrings) {
record._keyString = _displayFilter.toKeyDisplayString(exchange);
record._valueString = _displayFilter.toValueDisplayString(exchange);
}
if (forward) {
records[count] = record;
} else {
records[maxCount - count - 1] = record;
}
}
}
} catch (final Exception e) {
throw new WrappedRemoteException(e);
} finally {
exchange.ignoreMVCCFetch(false);
}
if (count < maxCount) {
final LogicalRecord[] trimmed = new LogicalRecord[count];
System.arraycopy(records, forward ? 0 : maxCount - count, trimmed, 0, count);
records = trimmed;
}
return records;
}
@Override
public LogicalRecordCount getLogicalRecordCount(final String volumeName, final String treeName,
final String keyFilterString, final KeyState fromKey, final Key.Direction direction, final int maxCount)
throws RemoteException {
int count = 0;
Exchange exchange = null;
KeyState endKeyState = null;
try {
exchange = _persistit.getExchange(volumeName, treeName, false);
exchange.getAuxiliaryKey2().clear();
KeyFilter filter = null;
if (keyFilterString != null && keyFilterString.length() > 0) {
filter = new KeyFilter(keyFilterString);
}
fromKey.copyTo(exchange.getKey());
for (; count < maxCount; count++) {
if (!exchange.traverse(direction, filter, 0)) {
break;
} else {
exchange.getKey().copyTo(exchange.getAuxiliaryKey2());
}
}
endKeyState = new KeyState(exchange.getAuxiliaryKey2());
} catch (final Exception pe) {
throw new WrappedRemoteException(pe);
} finally {
if (exchange != null)
_persistit.releaseExchange(exchange);
}
return new LogicalRecordCount(endKeyState, count);
}
/**
* Returns an array containing a <code>RecordInfo</code> element for each
* record in the page specified by <code>volumeName</code> and
* <code>pageAddress</code>. If Persistit is not initialized, or if there is
* no unique <code>Volume</code> for the specified <code>volumeName</code>,
* or if there is no page associated with the specified
* <code>pageAddress</code> or if there is any transient condition that
* causes the attempt to retrieve records to fail, then this method returns
* an empty array.
*
* @param volumeName
*
* @param pageAddress
*
* @return the array
*/
@Override
public RecordInfo[] getRecordInfoArray(final String volumeName, final long pageAddress) throws RemoteException {
final Volume volume = _persistit.getVolume(volumeName);
if (volume == null)
return new RecordInfo[0];
try {
final Buffer buffer = volume.getPool().getBufferCopy(volume, pageAddress);
return buffer.getRecords();
} catch (final PersistitException pe) {
throw new WrappedRemoteException(pe);
}
}
/**
* <p>
* Returns an array of {@link BufferInfo} objects reflecting the states of
* selected buffers from the <code>BufferPool</code> for the specified
* <code>bufferSize</code>. The selection criteria include the
* <code>traversalType</code>, <code>includeMask</code> and
* <code>excludeMask</code>. See {@link #populateBufferInfoArray} for a
* similar method that reuses a previously obtained result array.
* </p>
* <p>
* The <code>traversalType</code> must be one of the following:
* <dl>
* <dt>0</dt>
* <dd>all buffers in the buffer pool, in order by <code>poolIndex</code>.</dd>
* <dt>1</dt>
* <dd>buffers on the least-recently-used queue, ordered from least- to
* most-recently used.</dd>
* <dt>2</dt>
* <dd>Buffers on the invalid buffer queue. These buffers will be consumed
* first whenever a new page is copied into the pool.</dd>
* </dl>
* </p>
* <p>
* The <code>includeMask</code> and <code>excludeMask</code> are applied to
* each buffer's state to determine whether that buffer should be included
* in the set returned by this method. If <code>includeMask</code> is
* <code>null</code> then all buffers are included. Otherwise, only those
* buffers whose state is selected by <code>includeMask</code> and is not
* selected by <code>excludeMask</code> are included. Mask values are
* Strings in which each character denotes an attribute of a
* <code>Buffer</code> to be included or excluded from the selection. These
* characters are as follows:
* <dl>
* <dt>v</dt>
* <dd>Buffer must be VALID</dd>
* <dt>d</dt>
* <dd>Buffer must be DIRTY</dd>
* <dt>w</dt>
* <dd>Buffer must have a WRITER claim</dd>
* <dt>r</dt>
* <dd>Buffer must have a READER claim</dd>
* <dt>p</dt>
* <dd>Buffer must be PERMANENT. The head page for each {@link Volume}
* occupies a PERMANENT buffer.</dd> </dd>
* </p>
* <p>
* If Persistit is not initialized then this method returns an empty array.
* </p>
*
* @param bufferSize
* the buffer size of interest
*
* @param traversalType
* the traversal type, described above
*
* @param includeMask
* the buffer selection include mask, described above
*
* @param excludeMask
* the buffer selection exclude mask, described above
*
* @return the array
*/
@Override
public BufferInfo[] getBufferInfoArray(final int bufferSize, final int traversalType, final String includeMask,
final String excludeMask) {
final BufferPool pool = _persistit.getBufferPool(bufferSize);
if (pool == null)
return new BufferInfo[0];
BufferInfo[] results = new BufferInfo[pool.getBufferCount()];
final int count = pool.populateInfo(results, traversalType, makeStatus(includeMask), makeStatus(excludeMask));
if (count < results.length) {
final BufferInfo[] temp = new BufferInfo[count];
System.arraycopy(results, 0, temp, 0, count);
results = temp;
}
return results;
}
/**
* Returns a <code>BufferInfo</code> reflecting the status of the buffer
* containing the page specified by the supplied <code>volumeName</code> and
* <code>pageAddress</code>. If Persisit is not initialized or of the
* attempt the find the specified page fails, this method returns
* <code>null</code>
*
* @param volumeName
* the name of the volume
*
* @param pageAddress
* the page address
*
* @return the BufferInfo for the buffer containing the designated page, of
* <code>null</code> if there is none.
*/
@Override
public BufferInfo getBufferInfo(final String volumeName, final long pageAddress) throws RemoteException {
final Volume volume = _persistit.getVolume(volumeName);
if (volume == null)
return null;
try {
final Buffer buffer = volume.getPool().getBufferCopy(volume, pageAddress);
final BufferInfo info = new BufferInfo();
buffer.populateInfo(info);
return info;
} catch (final PersistitException pe) {
throw new WrappedRemoteException(pe);
}
}
/**
* Return a <code>BufferInfo</code> reflecting the state of a page
* containing the specified key. The <code>volumeName</code> and
* <code>treeName</code> parameters specify a {@link Tree} in which to seach
* for the key. The <code>level</code> parameter indicates whether the data
* page, or one of the pages on the index path to that data page should be
* returned. Level 0 refers to the data path, level 1 is the lowest index
* level, and level d-1 where d is the number of levels in the the tree
* represents the three's root page.
* <p>
* Specify <code>treeName</code> as <code>null</code> to access the volume's
* directory tree.
*
* @param volumeName
* the name of the volume
* @param treeName
* the name of the tree within the volume, or <code>null</code>
* for the directory tree
* @param key
* a <code>KeyState</code> representing a key
* @param level
* tree level: 0 for root, 1...d-1 for index pages of a tree
* having depth d.
* @return a <code>BufferInfo</code> object reflecting the selected page, or
* <code>null</code> if the specified tree does not exist.
* @throws RemoteException
*/
@Override
public BufferInfo getBufferInfo(final String volumeName, final String treeName, final KeyState key, final int level)
throws RemoteException {
try {
Exchange exchange;
final Volume volume = _persistit.getVolume(volumeName);
if (volume == null) {
return null;
}
if (treeName == null) {
exchange = volume.getStructure().directoryExchange();
} else {
exchange = _persistit.getExchange(volume, treeName, false);
}
key.copyTo(exchange.getKey());
final Buffer buffer = exchange.fetchBufferCopy(level);
final BufferInfo info = new BufferInfo();
buffer.populateInfo(info);
return info;
} catch (final TreeNotFoundException tnfe) {
return null;
} catch (final PersistitException pe) {
throw new WrappedRemoteException(pe);
}
}
/**
* <p>
* Populates a supplied array of {@link BufferInfo} objects to reflect the
* current states of selected buffers from the <code>BufferPool</code> for
* the specified <code>bufferSize</code>. The selection criteria include the
* <code>traversalType</code>, <code>includeMask</code> and
* <code>excludeMask</code>. See {@link #getBufferInfoArray} for a similar
* method that simply returns a fresh array on each invocation. This method
* is available for management applications that need to perform frequently
* refreshes.
* </p>
* <p>
* This method returns the actual number of buffers selected by the supplied
* criteria. This number may be larger than the size of the supplied array;
* in this case, information about the first N buffers in the set is
* returned in the array, where N is the size of the array. An application
* can use the {@link BufferPoolInfo#getBufferCount} method to determine the
* maximum number of <code>BufferInfo</code> objects that could be
* populated.
* </p>
* <p>
* The <code>traversalType</code> must be one of the following:
* <dl>
* <dt>0</dt>
* <dd>all buffers in the buffer pool, in order by <code>poolIndex</code>.</dd>
* <dt>1</dt>
* <dd>buffers on the least-recently-used queue, ordered from least- to
* most-recently used.</dd>
* <dt>2</dt>
* <dd>Buffers on the invalid buffer queue. These buffers will be consumed
* first whenever a new page is copied into the pool.</dd>
* </dl>
* </p>
* <p>
* The <code>includeMask</code> and <code>excludeMask</code> are applied to
* each buffer's state to determine whether that buffer should be included
* in the set returned by this method. If <code>includeMask</code> is
* <code>null</code> then all buffers are included. Otherwise, only those
* buffers whose state is selected by <code>includeMask</code> and is not
* selected by <code>excludeMask</code> are included. Mask values are
* Strings in which each character denotes an attribute of a
* <code>Buffer</code> to be included or excluded from the selection. These
* characters are as follows:
* <dl>
* <dt>v</dt>
* <dd>Buffer must be VALID</dd>
* <dt>d</dt>
* <dd>Buffer must be DIRTY</dd>
* <dt>w</dt>
* <dd>Buffer must have a WRITER claim</dd>
* <dt>r</dt>
* <dd>Buffer must have a READER claim</dd>
* <dt>p</dt>
* <dd>Buffer must be PERMANENT. The head page for each {@link Volume}
* occupies a PERMANENT buffer.</dd>
* </dd>
* <p>
* If Persistit is not initialized then this method returns an empty array.
* </p>
*
* @param bufferSize
* the buffer size of interest
*
* @param traversalType
* the traversal type, described above
*
* @param includeMask
* the buffer selection include mask, described above
*
* @param excludeMask
* the buffer selection exclude mask, described above
*
* @return the array
*/
@Override
public int populateBufferInfoArray(final BufferInfo[] results, final int bufferSize, final int traversalType,
final String includeMask, final String excludeMask) {
final BufferPool pool = _persistit.getBufferPool(bufferSize);
if (pool == null)
return -1;
final int count = pool.populateInfo(results, traversalType, makeStatus(includeMask), makeStatus(excludeMask));
return count;
}
private static int makeStatus(final String statusCode) {
if (statusCode == null)
return 0;
int status = 0;
if (statusCode.indexOf('v') >= 0)
status |= SharedResource.VALID_MASK;
if (statusCode.indexOf('d') >= 0)
status |= SharedResource.DIRTY_MASK;
if (statusCode.indexOf('r') >= 0)
status |= SharedResource.CLAIMED_MASK;
if (statusCode.indexOf('w') >= 0)
status |= SharedResource.WRITER_MASK;
if (statusCode.indexOf('p') >= 0)
status |= SharedResource.FIXED_MASK;
if (statusCode.indexOf('a') >= 0)
status |= (SharedResource.FIXED_MASK | SharedResource.WRITER_MASK | SharedResource.CLAIMED_MASK
| SharedResource.DIRTY_MASK | SharedResource.VALID_MASK);
// select none
if (status == 0)
status = 0x80000000;
return status;
}
/**
* Returns an array containing a <code>VolumeInfo</code> element for each
* open volume. If Persistit is not initialized then this method returns an
* empty array. </p>
*
* @return The array
*/
@Override
public VolumeInfo[] getVolumeInfoArray() {
final List<Volume> volumes = _persistit.getVolumes();
final VolumeInfo[] result = new VolumeInfo[volumes.size()];
for (int index = 0; index < volumes.size(); index++) {
result[index] = new VolumeInfo(volumes.get(index));
}
Arrays.sort(result);
return result;
}
/**
* Returns the <code>VolumeInfo</code> for the volume specified by the
* supplied <code>volumeName</code>. If Persisit is not initialized or there
* is no unique volume corresponding with the supplied name, then this
* method returns <code>null</code>.
*
* @param volumeName
*
* @return the <code>VolumeInfo</code>
*/
@Override
public VolumeInfo getVolumeInfo(final String volumeName) {
final Volume volume = _persistit.getVolume(volumeName);
if (volume == null)
return null;
else
return new VolumeInfo(volume);
}
/**
* Returns an array containing a <code>TreeInfo</code> element for each
* <code>Tree</code> in the specified volume. If there is no volume with the
* specified name or if Persistit is not initialized then this method
* returns an empty array.
*
* @param volumeName
* The name (or unique partial name) of the volume for which
* information is being requested.
*
* @return The array
*/
@Override
public TreeInfo[] getTreeInfoArray(final String volumeName) throws RemoteException {
if (volumeName == null) {
return new TreeInfo[0];
}
final Volume volume = _persistit.getVolume(volumeName);
if (volume == null)
return new TreeInfo[0];
try {
final String[] treeNames = volume.getTreeNames();
TreeInfo[] results = new TreeInfo[treeNames.length + 1];
int count = 0;
results[count++] = new TreeInfo(volume.getDirectoryTree());
for (int index = 0; index < treeNames.length; index++) {
final TreeInfo info = volume.getTreeInfo(treeNames[index]);
if (info != null) {
results[count++] = info;
}
}
if (count < results.length) {
final TreeInfo[] temp = new TreeInfo[count];
System.arraycopy(results, 0, temp, 0, count);
results = temp;
}
return results;
} catch (final PersistitException pe) {
throw new WrappedRemoteException(pe);
}
}
/**
* Returns a <code>TreeInfo</code> for a specified <code>Volume</code> and
* </code>Tree</code>. If Persisit is not initialized, or if no no volume or
* tree with corresponding names is found, or if there is a transient error
* in acquiring the information, this method returns <code>null</code>.
*
* @param volumeName
* The name (or partial name) of the volume
*
* @param treeName
* The name of the tree
*
* @return the <code>TreeInfo</code>
*/
@Override
public TreeInfo getTreeInfo(final String volumeName, final String treeName) throws RemoteException {
final Volume volume = _persistit.getVolume(volumeName);
if (volume == null)
return null;
try {
Tree tree = null;
if (VolumeStructure.DIRECTORY_TREE_NAME.equals(treeName)) {
tree = volume.getDirectoryTree();
} else {
tree = volume.getTree(treeName, false);
}
if (tree != null)
return new TreeInfo(tree);
} catch (final PersistitException pe) {
throw new WrappedRemoteException(pe);
}
return null;
}
/**
* Returns a Class definition for a class specified by its name. This allows
* a remote UI instance connected through RMI to load classes that are
* available within the running Persistit instance so that encoded objects
* can be inspected within the UI. The implementation of this method should
* instantiate a new <code>ClassLoader</code> instance so that unreferenced
* loaded classes may subsequently be garbage collected.
*
* @param className
* Fully qualified class name.
* @return The <code>Class</code>, or <code>null</code> if an exception
* occurred while attempting to acquire the Class.
* @throws RemoteException
*/
@Override
public Class getRemoteClass(final String className) throws RemoteException {
//
// Need a subclass with a public constructor.
//
final ClassLoader loader = new ClassLoader() {
};
try {
final Class clazz = loader.loadClass(className);
return clazz;
} catch (final ClassNotFoundException cnfe) {
throw new WrappedRemoteException(cnfe);
}
}
@Override
public int parseKeyFilterString(final String keyFilterString) throws RemoteException {
final KeyParser parser = new KeyParser(keyFilterString);
if (parser.parseKeyFilter() != null)
return -1;
else
return parser.getIndex();
}
/**
* <p>
* Decodes the content of the supplied <code>ValueState</code> as an array
* of Objects. Usually this array has one element containing the single
* Object value encoded in the <code>ValueState</code>. However, if multiple
* items were written to the original <code>Value</code> from which the
* <code>ValueState</code> was derived in
* <a href="com.persistit.Value.html#_streamMode">Stream Mode</a>, this
* method returns all of the encoded objects.
* </p>
* <p>
* If the <code>valueState</code> represents an undefined value, this method
* returns an array of length zero. If the <code>valueState</code> encodes
* a value of <code>null</code>, then this method returns an array containing
* one element which is <code>null</code>.
* </p>
*
* @param valueState Representation of an encoded {@link Value).
*
* @param context Object passed to any {@link ValueCoder} used in
* decoding the value. May be <code>null</code>
*
* @return Array of zero or more Objects encoded
* in the <code>ValueState</code>
* @throws RemoteException
*/
@Override
public Object[] decodeValueObjects(final ValueState valueState, final CoderContext context) throws RemoteException {
try {
final Value value = new Value(_persistit);
valueState.copyTo(value);
value.setStreamMode(true);
final Vector vector = new Vector();
while (value.hasMoreItems()) {
vector.addElement(value.get(null, context));
}
final Object[] result = new Object[vector.size()];
for (int index = 0; index < result.length; index++) {
result[index] = vector.get(index);
}
return result;
} catch (final Exception e) {
throw new WrappedRemoteException(e);
}
}
/**
* Decodes the content of the supplied <code>KeyState</code> as an array of
* Objects, one object per <a href="com.persisit.Key.html#_keySegments> key
* segment</a>.
*
* @param keyState
* Representation of an encoded {@link Key}.
*
* @param context
*
* @return
*
* @throws RemoteException
*/
@Override
public Object[] decodeKeyObjects(final KeyState keyState, final CoderContext context) throws RemoteException {
try {
final Key key = new Key(_persistit);
keyState.copyTo(key);
final int size = key.getDepth();
final Object[] result = new Object[size];
for (int index = 0; index < size; index++) {
result[index] = key.decode(null, context);
}
return result;
} catch (final Exception e) {
throw new WrappedRemoteException(e);
}
}
/**
* Starts a long-running utility task, such as the integrity checker. The
* supplied className must identify a subclass of {@link com.persistit.Task}
* . The number and format of the arguments is specific to the utility task.
* The returned long value is a unique task ID value used in subsequent
* calls to {@link #queryTaskStatus}.
*
* @param description
* Readable description of this task
* @param owner
* Hostname or username of the user who requested this task
* @param className
* Class name of task to run, e.g.,
* <code>com.persistit.IntegrityCheck</code>.
* @param args
* Task-specific parameters
* @param maximumTime
* Maximum wall-clock time (in milliseconds) this Task will be
* allowed to run, or 0 for unbounded time
* @param verbosity
* Verbosity level, one of {@link Task#LOG_NORMAL} or
* {@link Task#LOG_NORMAL}.
* @return Task identifier Unique ID for the running task
* @throws RemoteException
*/
@Override
public synchronized long startTask(final String description, final String owner, final String commandLine,
final long maximumTime, final int verbosity) throws RemoteException {
try {
final Task task = CLI.parseTask(_persistit, commandLine);
if (task == null) {
throw new WrappedRemoteException(new IllegalArgumentException("Unknown task " + commandLine));
}
final long taskId = ++_taskIdCounter;
task.setPersistit(_persistit);
task.setup(taskId, description, owner, maximumTime, verbosity);
_tasks.put(new Long(taskId), task);
task.start();
return taskId;
} catch (final Exception ex) {
throw new WrappedRemoteException(ex);
}
}
/**
* Queries the current status of one or all tasks. If the specified taskId
* value is -1, this method returns status information for all currently
* active tasks and clears any tasks that have completed.
*
* @param taskId
* Task ID for a selected Task, or -1 for all Tasks.
* @param details
* <code>true</code> to populate each returned
* <code>TaskStatus</code> object with all new messages posted by
* the task.
* @param clearMessages
* <code>true</code> to clear all received messages from the
* task.
* @throws RemoteException
*/
@Override
public synchronized TaskStatus[] queryTaskStatus(final long taskId, final boolean details,
final boolean clearMessages) {
return queryTaskStatus(taskId, details, clearMessages, true);
}
/**
* Queries the current status of one or all tasks. If the specified taskId
* value is -1, this method returns status information for all currently
* active tasks.
*
* @param taskId
* Task ID for a selected Task, or -1 for all Tasks.
* @param details
* <code>true</code> to populate each returned
* <code>TaskStatus</code> object with all new messages posted by
* the task.
* @param clearMessages
* <code>true</code> to clear all received messages from the
* task.
* @param clearTasks
* <code>true</code> to remove the task's status if it has
* finished, failed or expired.
* @throws RemoteException
*/
@Override
public synchronized TaskStatus[] queryTaskStatus(final long taskId, final boolean details,
final boolean clearMessages, final boolean clearTasks) {
if (taskId == -1) {
final int size = _tasks.size();
int index = 0;
final TaskStatus[] result = new TaskStatus[size];
for (final Iterator<Task> iterator = _tasks.values().iterator(); iterator.hasNext();) {
final Task task = iterator.next();
final TaskStatus ts = new TaskStatus();
task.populateTaskStatus(ts, details, clearMessages);
result[index++] = ts;
if (clearTasks && Task.isFinalStatus(task._state)) {
iterator.remove();
}
}
return result;
} else {
final Task task = _tasks.get(taskId);
if (task == null) {
return new TaskStatus[0];
} else {
final TaskStatus ts = new TaskStatus();
task.populateTaskStatus(ts, details, clearMessages);
if (clearTasks && Task.isFinalStatus(task._state)) {
_tasks.remove(taskId);
}
return new TaskStatus[] { ts };
}
}
}
/**
* Suspend or resumes the task(s) identified by <code>taskId</code>. If
* <code>taskId</code> is -1, all tasks are modified.
*
* @param taskId
* Task ID for a selected Task, or -1 for all Tasks.
* @param suspend
* <code>true</code> to suspend the task, <code>false</code> to
* allow it to resume.
* @throws RemoteException
*/
@Override
public synchronized void setTaskSuspended(final long taskId, final boolean suspend) {
if (taskId == -1) {
for (final Iterator<Task> iterator = _tasks.values().iterator(); iterator.hasNext();) {
final Task task = iterator.next();
if (suspend)
task.suspend();
else
task.resume();
}
} else {
final Task task = _tasks.get(new Long(taskId));
if (task != null) {
if (suspend)
task.suspend();
else
task.resume();
}
}
}
/**
* Stops and optionally removes a task specified by its id value. If the
* task is currently running, this method stops it. If <code>remove</code>
* is <code>true</code> this method also removes the Task from the task
* list. Otherwise the task remains on the task list in the
* {@link Task#STATE_ENDED} state.
*
* @param taskId
* Task ID for a selected Task.
* @throws RemoteException
*/
@Override
public synchronized void stopTask(final long taskId, final boolean remove) {
if (taskId == -1) {
for (final Iterator<Task> iterator = _tasks.values().iterator(); iterator.hasNext();) {
final Task task = iterator.next();
task.stop();
}
if (remove) {
_tasks.clear();
}
} else {
final Task task = _tasks.get(new Long(taskId));
if (task != null) {
task.stop();
if (remove) {
_tasks.remove(new Long(task._taskId));
}
}
}
}
/**
* Removes finished, expired, stopped or failed tasks from the task list. \
* This method does not affected running or suspended tasks.
*
* @param taskId
* Task ID for a selected task, or -1 for all tasks.
*
* @throws RemoteException
*/
@Override
public synchronized void removeFinishedTasks(final long taskId) {
if (taskId == -1) {
for (final Iterator<Task> iterator = _tasks.values().iterator(); iterator.hasNext();) {
final Task task = iterator.next();
if (Task.isFinalStatus(task._state)) {
iterator.remove();
}
}
} else {
final Long key = Long.valueOf(taskId);
final Task task = _tasks.get(key);
if (task != null && Task.isFinalStatus(task._state)) {
_tasks.remove(key);
}
}
}
@Override
public DisplayFilter getDisplayFilter() {
return _displayFilter;
}
@Override
public void setDisplayFilter(final DisplayFilter displayFilter) {
_displayFilter = displayFilter;
}
void register(String hostName, final int rmiPort, final int serverPort) {
try {
final ManagementImpl impl = (ManagementImpl) _persistit.getManagement();
if (hostName == null && rmiPort != -1) {
try {
if (hostName == null) {
final InetAddress addr = InetAddress.getLocalHost();
try {
hostName = addr.getHostName() + ":" + rmiPort;
} catch (final Exception e) {
hostName = addr.getHostAddress() + ":" + rmiPort;
}
}
} catch (final NumberFormatException nfe) {
}
}
if (rmiPort != -1 && _localRegistryPort != rmiPort) {
LocateRegistry.createRegistry(rmiPort);
_localRegistryPort = rmiPort;
}
if (hostName != null && hostName.length() > 0) {
final String name = "//" + hostName + "/PersistitManagementServer";
UnicastRemoteObject.exportObject(impl, serverPort);
Naming.rebind(name, impl);
impl._registered = true;
impl._registeredHostName = hostName;
_persistit.getLogBase().rmiServerRegistered.log(hostName);
}
} catch (final Exception exception) {
_persistit.getLogBase().rmiRegisterException.log(hostName, exception);
}
}
void unregister() {
if (_registered) {
try {
final ManagementImpl impl = (ManagementImpl) _persistit.getManagement();
UnicastRemoteObject.unexportObject(impl, true);
_registered = false;
_persistit.getLogBase().rmiServerUnregister.log(_registeredHostName);
} catch (final Exception exception) {
_persistit.getLogBase().rmiUnregisterException.log(_registeredHostName, exception);
}
}
}
@Override
public String launch(final String commandLine) {
try {
final Task task = CLI.parseTask(_persistit, commandLine);
if (task == null) {
return "Invalid task " + commandLine;
}
if (task.isImmediate()) {
task.runTask();
task.setPersistit(null);
return task.getStatusDetail();
}
return launch(task, commandLine);
} catch (final Exception ex) {
return "Failed: " + ex.toString();
}
}
@Override
public VolumeInfo volumeByName(final String volumeName) throws RemoteException {
return getVolumeInfo(volumeName);
}
@Override
public synchronized String launch(final Task task, final String description) throws RemoteException {
try {
final long taskId = taskId();
task.setup(taskId, description, Thread.currentThread().getName(), 0, 5);
_tasks.put(new Long(taskId), task);
task.start();
return Long.toString(taskId);
} catch (final Exception ex) {
return "Failed: " + ex.toString();
}
}
@Override
public String execute(final String commandLine) {
try {
final Task task = CLI.parseTask(_persistit, commandLine);
if (task == null) {
return "Invalid task " + commandLine;
}
task.runTask();
task.setPersistit(null);
return task.getStatusDetail();
} catch (final Exception ex) {
return "Failed: " + ex.toString();
}
}
public synchronized long taskId() {
return ++_taskIdCounter;
}
}