org.snmp4j.agent.mo.DefaultMOTable Maven / Gradle / Ivy
/*_############################################################################
_##
_## SNMP4J-Agent 2 - DefaultMOTable.java
_##
_## Copyright (C) 2005-2014 Frank Fock (SNMP4J.org)
_##
_## 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 org.snmp4j.agent.mo;
import java.io.*;
import java.util.*;
import org.snmp4j.*;
import org.snmp4j.agent.*;
import org.snmp4j.agent.io.*;
import org.snmp4j.agent.request.*;
import org.snmp4j.agent.util.*;
import org.snmp4j.log.*;
import org.snmp4j.mp.*;
import org.snmp4j.smi.*;
/**
* The DefaultMOTable
class is the default implementation of the
* {@link MOTable} class. For most use cases, it is not necessary to customize
* this class through deriving your own sub-class. Instead, using a different
* {@link MOTableModel} as table data provider is sufficient.
*
* The default table model can be used to hold the data of a SNMP conceptual
* table as real tabular data. If you want to implement a virtual table, you
* will have to directly implement the interfaces {@link MOTableModel} or
* {@link MOMutableTableModel} to access the data based on the actual view.
*
* @author Frank Fock
* @version 2.2
*/
public class DefaultMOTable>
implements MOTable, MOScope, SerializableManagedObject {
private static LogAdapter logger =
LogFactory.getLogger(DefaultMOTable.class);
private OID oid;
private MOTableIndex indexDef;
private C[] columns;
protected M model;
private boolean isVolatile;
protected WeakHashMap> newRows;
protected WeakHashMap> pendingChanges;
protected transient Vector moChangeListeners;
protected transient Vector> moTableRowListeners;
private transient WeakHashMap walkCache;
private static Comparator columnComparator = new Comparator() {
public int compare(Object o1, Object o2) {
int id1 = (o1 instanceof MOColumn) ?
((MOColumn)o1).getColumnID() : (Integer) o1;
int id2 = (o2 instanceof MOColumn) ?
((MOColumn)o2).getColumnID() : (Integer) o2;
return id1 - id2;
}
};
@SuppressWarnings("unchecked")
public DefaultMOTable(OID oid, MOTableIndex indexDef, C[] columns) {
this(oid, indexDef, columns, (M)new DefaultMOMutableTableModel());
}
/**
* Creates a new SNMP table with the specified "Entry" OID, INDEX, columns,
* and {@link MOTableModel}.
*
* @param oid
* the OID of the SNMP table's Entry object. If the table is ifTable
* (1.3.6.1.2.1.2.2) then the OID to provide is 1.3.6.1.2.1.2.2.1
* which is the ifEntry OID. By SMI rule, you can simply append ".1" to
* the table OID.
* @param indexDef
* the index definition of the table based on the INDEX clause of the
* table MIB definition.
* @param columns
* the column definitions which may also include non-accessible columns.
* @param model
* the table model holding the table data.
*/
public DefaultMOTable(OID oid, MOTableIndex indexDef,
C[] columns, M model) {
this.oid = oid;
this.indexDef = indexDef;
this.columns = columns;
this.model = model;
registerColumns();
}
private void registerColumns() {
for (MOColumn> column : columns) {
column.setTable(this);
}
}
public MOTableCellInfo getCellInfo(OID oid) {
return new CellInfo(this, oid);
}
@SuppressWarnings("unchecked")
public int getColumnIndex(int id) {
return Arrays.binarySearch(columns, id, columnComparator);
}
public C getColumn(int index) {
return columns[index];
}
public int getColumnCount() {
return columns.length;
}
/**
* Creates a new row for this table with the supplied index and initial
* values. If the underlying table model is not a {@link MOMutableTableModel}
* instance or if one of the {@link MOTableRowListener} deny the row creation
* attempt then null
will be returned.
* @param index
* the index OID of the new row.
* @param initialValues
* the initial values that should be assigned to the new row. If the array
* contains less values than this table has columns, default values will
* be created for the missing columns.
* @return
* the created MOTableRow
instance or null
if
* the row cannot be created.
*/
@SuppressWarnings("unchecked")
public R createRow(OID index, Variable[] initialValues) {
if (model instanceof MOMutableTableModel) {
Variable[] values = initialValues;
if (values.length < getColumnCount()) {
values = getDefaultValues();
System.arraycopy(initialValues, 0, values, 0, initialValues.length);
}
R row = ((MOMutableTableModel)model).createRow(index, values);
MOTableRowEvent rowEvent =
new MOTableRowEvent(this, this, row, MOTableRowEvent.CREATE, true);
fireRowChanged(rowEvent);
if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
return row;
}
}
return null;
}
@Override
public R addNewRow(OID index, Variable[] initialValues) {
R newRow = createRow(index, initialValues);
if (newRow != null) {
addRow(newRow);
}
return newRow;
}
public R createRow(OID index) {
return createRow(index, getDefaultValues());
}
/**
* Adds the supplied row to the underlying table model and fires the
* appropriate {@link MOTableRowEvent}. Since this method is typically
* called during the commit phase of a SET request that creates a table,
* it should be avoided to return an error here. Instead, error checking
* should be placed in the {@link #prepare} method.
* @param row
* the MOTableRow
to add.
* @return
* true
if the row has been added or false
* if it could not be added.
*/
@SuppressWarnings("unchecked")
public boolean addRow(R row) {
if (model instanceof MOMutableTableModel) {
MOTableRowEvent rowEvent =
new MOTableRowEvent(this, this, row, MOTableRowEvent.ADD, true);
fireRowChanged(rowEvent);
if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
((MOMutableTableModel)model).addRow(row);
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public R removeRow(OID index) {
if (model instanceof MOMutableTableModel) {
R row = model.getRow(index);
if (row == null) {
return null;
}
MOTableRowEvent rowEvent =
new MOTableRowEvent(this, this, row, MOTableRowEvent.DELETE, true);
fireRowChanged(rowEvent);
if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
return ((MOMutableTableModel)model).removeRow(index);
}
}
return null;
}
/**
* Removes all rows from this table. Before a row is removed the
* corresponding DELETE event is fired and listeners may veto these
* events for all rows. Only if there is no veto, a row will be deleted.
* The number of deleted rows is then returned.
* @return
* the number of removed rows or -1 if the table model does not support
* row removal.
*/
public int removeAll() {
int count = 0;
if (model instanceof MOMutableTableModel) {
while (model.getRowCount() > 0) {
R row = model.firstRow();
if (row != null) {
MOTableRowEvent rowEvent =
new MOTableRowEvent(this, this, row, MOTableRowEvent.DELETE, true);
fireRowChanged(rowEvent);
if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
((MOMutableTableModel) model).removeRow(row.getIndex());
}
count++;
}
}
}
else {
count = -1;
}
return count;
}
public void commit(SubRequest request) {
OID cellOID = request.getVariableBinding().getOid();
MOTableCellInfo cell = getCellInfo(cellOID);
MOMutableColumn col = (MOMutableColumn) getColumn(cell.getColumn());
if (logger.isDebugEnabled()) {
logger.debug("Committing sub-request ("+
request.getVariableBinding()+") for column: "+col);
}
// Make sure changes are atomic -> sync whole table model
synchronized (model) {
R row;
if (hasNewRows(request.getRequest())) {
row = getNewRows(request.getRequest()).get(cell.getIndex());
// check if row has been added already
if (!model.containsRow(row.getIndex())) {
if (!addRow(row)) {
request.setErrorStatus(PDU.resourceUnavailable);
return;
}
}
}
else {
row = model.getRow(cell.getIndex());
}
Variable oldValue = null;
if (moChangeListeners != null) {
oldValue = row.getValue(cell.getColumn());
MOChangeEvent changeEvent =
new MOChangeEvent(this, new CellProxy(cell),
cell.getCellOID(),
oldValue,
request.getVariableBinding().getVariable(),
false);
fireBeforeMOChange(changeEvent);
}
ChangeSet changeSet = getPendingChangeSet(request, cell.getIndex());
// commit
col.commit(request, row, changeSet, cell.getColumn());
if (moChangeListeners != null) {
MOChangeEvent changeEvent =
new MOChangeEvent(this, new CellProxy(cell),
cell.getCellOID(),
oldValue,
request.getVariableBinding().getVariable(),
false);
fireAfterMOChange(changeEvent);
}
if (isChangeSetComplete(request, cell.getIndex(), cell.getColumn())) {
if (row instanceof MOMutableRow2PC) {
((MOMutableRow2PC) row).commitRow(request, changeSet);
}
if (moTableRowListeners != null) {
MOTableRowEvent rowEvent =
new MOTableRowEvent(this, this, row, MOTableRowEvent.UPDATED);
fireRowChanged(rowEvent);
}
}
}
}
public final OID getIndexPart(OID anyOID) {
int offset = oid.size()+1;
if ((anyOID.size() <= offset) || (!anyOID.startsWith(oid))) {
return null;
}
return new OID(anyOID.getValue(), offset, anyOID.size() - offset);
}
public OID getCellOID(OID index, int col) {
OID retval = new OID(oid);
retval.append(columns[col].getColumnID());
retval.append(index);
return retval;
}
private MOTableCellInfo getNextCell(int col,
OID indexLowerBound,
boolean isLowerBoundIncluded) {
for (int i=col; i it = model.tailIterator(indexLowerBound);
if (!it.hasNext()) {
if (indexLowerBound == null) {
return null;
}
indexLowerBound = null;
isLowerBoundIncluded = true;
}
else {
if ((indexLowerBound != null) && (!isLowerBoundIncluded)) {
MOTableRow row = it.next();
if (row.getIndex().compareTo(indexLowerBound) > 0) {
// the specified index does not exists so we can use this next one:
return new CellInfo(this, row.getIndex(), i, columns[i].getColumnID(),
row);
}
}
indexLowerBound = null;
isLowerBoundIncluded = true;
if (it.hasNext()) {
MOTableRow row = it.next();
if (row == null) {
continue;
}
return new CellInfo(this, row.getIndex(), i, columns[i].getColumnID(), row);
}
}
}
return null;
}
public OID find(MOScope range) {
MOTableCellInfo cellInfo = findCell(range, null);
if (cellInfo != null) {
return cellInfo.getCellOID();
}
return null;
}
protected MOTableCellInfo findCell(MOScope range, SubRequest request) {
synchronized (model) {
update(range, request);
// determine column
if (model.isEmpty()) {
return null;
}
MOTableCellInfo cellInfo = getCellInfo(range.getLowerBound());
int col = cellInfo.getColumn();
boolean exactMatch = true;
if (col < 0) {
col = (-col) - 1;
exactMatch = false;
}
if (col >= columns.length) {
return null;
}
boolean lowerIncluded = (!exactMatch) || range.isLowerIncluded();
RowCacheEntry rowEntry = null;
if (request != null) {
rowEntry = getWalkCacheEntry(request, cellInfo, lowerIncluded);
}
MOTableCellInfo next;
if (rowEntry != null) {
next = new CellInfo(this, rowEntry.row.getIndex(),
col, cellInfo.getColumnID(), rowEntry.row);
}
else {
next = getNextCell(col, cellInfo.getIndex(), lowerIncluded);
if ((request != null) && (next != null) && (next.getColumn() == col)) {
addWalkCacheEntry(request, cellInfo.getIndex(), lowerIncluded,
((CellInfo)next).row);
}
}
if (next != null) {
OID cellOID = next.getCellOID();
if (range.isCovered(new OIDScope(cellOID))) {
return next;
}
}
}
return null;
}
private void addWalkCacheEntry(SubRequest request,
OID lowerBound,
boolean lowerIncluded,
MOTableRow row) {
if (walkCache == null) {
walkCache = new WeakHashMap(4);
}
walkCache.put(request.getRequest(),
new RowCacheEntry(row, lowerBound, lowerIncluded));
}
private RowCacheEntry getWalkCacheEntry(SubRequest request,
MOTableCellInfo cellInfo,
boolean lowerIncluded) {
if (walkCache != null) {
RowCacheEntry entry = walkCache.get(request.getRequest());
if (entry == null) {
return null;
}
if (((entry.searchLowerBound == null) && (cellInfo.getIndex() == null)) ||
((entry.searchLowerBound != null) &&
(entry.searchLowerBound.equals(cellInfo.getIndex())) &&
(lowerIncluded == entry.searchLowerBoundIncluded))) {
return entry;
}
}
return null;
}
public MOScope getScope() {
return this;
}
public Variable getValue(OID cellOID) {
MOTableCellInfo cell = getCellInfo(cellOID);
if ((cell.getIndex() != null) &&
(cell.getColumn() >= 0) && (cell.getColumn() < columns.length)) {
return getValue(cell.getIndex(), cell.getColumn());
}
return null;
}
public Variable getValue(OID index, int col) {
MOTableRow row = model.getRow(index);
return getValue(row, col);
}
@SuppressWarnings("unchecked")
protected Variable getValue(MOTableRow row, int col) {
if ((row != null) && (col >= 0) && (col < row.size())) {
return columns[col].getValue(row, col);
}
return null;
}
/**
* Update the content of this table that is covered by the supplied
* scope.
*
* This method is part of the {@link UpdatableManagedObject} interface.
* Although the {@link DefaultMOTable} does not implement that interface,
* subclasses of this class may do so easily by overriding this hook-up
* method.
*
* @param updateScope
* the scope to update. If null
the whole managed object is
* updated.
* @since 1.2
* @see #update(MOScope range, SubRequest request)
*/
public void update(MOScope updateScope) {
// nothing to do by default -> overwrite to update an updatable table.
}
/**
* Update this table for the supplied search range and sub-request if it has
* not yet been updated for that request.
*
* By default, the
* {@link #update(MOScope updateScope)} is being called on behalf of this
* method call (which itself does not nothing by default). You may choose
* either to implement the {@link UpdatableManagedObject} interface and
* implement its interface in a subclass. Then it is recommended to overwrite
* this method by an empty method. Otherwise, do not implement the
* {@link UpdatableManagedObject} interface.
*
*
* @param range
* the search range.
* @param request
* the sub-request triggered the update or null
if that
* request cannot be determined.
*/
protected void update(MOScope range, SubRequest request) {
Object updateMarker = null;
if ((request != null) && (request.getRequest() != null)) {
updateMarker = request.getRequest().getProcessingUserObject(getOID());
}
if (updateMarker == null) {
if ((request != null) && (request.getRequest() != null)) {
request.getRequest().setProcessingUserObject(getOID(), new Object());
}
update(range);
}
}
public void get(SubRequest request) {
OID cellOID = request.getVariableBinding().getOid();
MOTableCellInfo cell = getCellInfo(cellOID);
if ((cell.getIndex() != null) &&
(cell.getColumn() >= 0) && (cell.getColumn() < columns.length)) {
// update the table part affected by this query
update(request.getScope(), request);
MOColumn col = getColumn(cell.getColumn());
MOTableRow row = model.getRow(cell.getIndex());
if (row == null) {
request.getVariableBinding().setVariable(Null.noSuchInstance);
}
else if (col != null) {
col.get(request, row, cell.getColumn());
}
else {
request.getStatus().setErrorStatus(PDU.noAccess);
}
}
else {
if (cell.getColumn() >= 0) {
request.getVariableBinding().setVariable(Null.noSuchInstance);
}
else {
request.getVariableBinding().setVariable(Null.noSuchObject);
}
}
request.completed();
}
public boolean next(SubRequest request) {
DefaultMOScope scope = new DefaultMOScope(request.getScope());
MOTableCellInfo nextCell;
while ((nextCell = findCell(scope, request)) != null) {
if (columns[nextCell.getColumn()].getAccess().isAccessibleForRead()) {
Variable value;
// Use row instance from cell info as shortcut if available
if ((nextCell instanceof CellInfo) &&
(((CellInfo)nextCell).getRow() != null)) {
value = getValue(((CellInfo)nextCell).getRow(), nextCell.getColumn());
}
else {
value = getValue(nextCell.getIndex(), nextCell.getColumn());
}
if (value == null) {
scope.setLowerBound(nextCell.getCellOID());
scope.setLowerIncluded(false);
}
else {
request.getVariableBinding().setOid(nextCell.getCellOID());
request.getVariableBinding().setVariable(value);
request.completed();
return true;
}
}
else {
if (nextCell.getColumn()+1 < getColumnCount()) {
OID nextColOID = new OID(getOID());
nextColOID.append(columns[nextCell.getColumn()+1].getColumnID());
scope.setLowerBound(nextColOID);
scope.setLowerIncluded(false);
}
else {
return false;
}
}
}
return false;
}
@SuppressWarnings("unchecked")
public void prepare(SubRequest request) {
OID cellOID = request.getVariableBinding().getOid();
MOTableCellInfo cell = getCellInfo(cellOID);
if (cell.getIndex() == null) {
request.getStatus().setErrorStatus(PDU.inconsistentName);
return;
}
if ((cell.getColumn() >= 0) && (cell.getColumn() < columns.length)) {
MOColumn col = getColumn(cell.getColumn());
if (logger.isDebugEnabled()) {
logger.debug("Preparing sub-request ("+request.getVariableBinding()+")"+
" for column: "+col);
}
if ((col instanceof MOMutableColumn) &&
(col.getAccess().isAccessibleForWrite())) {
MOMutableColumn mcol = (MOMutableColumn)col;
// check index
if (getIndexDef().isValidIndex(cell.getIndex())) {
R row = model.getRow(cell.getIndex());
boolean newRow = false;
if (row == null) {
// look for already prepared row
row = getNewRows(request.getRequest()).get(cell.getIndex());
newRow = true;
}
if (row != null) {
prepare(request, cell, mcol, row, newRow);
request.completed();
}
else if (model instanceof MOMutableTableModel) {
if (logger.isDebugEnabled()) {
logger.debug("Trying to create new row '"+cell.getIndex()+"'");
}
MOMutableTableModel mmodel = (MOMutableTableModel)model;
// create new row
try {
row = createRow(request, cell, mmodel);
if (row == null) {
request.getStatus().setErrorStatus(PDU.noCreation);
}
else {
prepare(request, cell, mcol, row, true);
request.completed();
}
}
catch (UnsupportedOperationException ex) {
request.getStatus().setErrorStatus(PDU.noCreation);
}
}
else {
request.getStatus().setErrorStatus(PDU.noCreation);
}
}
else {
// invalid index
if (logger.isDebugEnabled()) {
logger.debug("Invalid index '"+cell.getIndex()+
"' for row creation in table "+getID());
}
request.getStatus().setErrorStatus(PDU.noCreation);
}
}
else {
// read-only column
request.getStatus().setErrorStatus(PDU.notWritable);
}
}
else {
request.getStatus().setErrorStatus(PDU.noCreation);
}
}
private R createRow(SubRequest request, MOTableCellInfo cell,
MOMutableTableModel mmodel)
throws UnsupportedOperationException
{
MOColumn col = getColumn(cell.getColumn());
if (!col.getAccess().isAccessibleForCreate()) {
// creation not allowed
return null;
}
Variable[] initialValues = new Variable[getColumnCount()];
getChangesFromRequest(cell.getIndex(), null, request,
initialValues, true, true);
R row = mmodel.createRow(cell.getIndex(), initialValues);
getNewRows(request.getRequest()).put(row.getIndex(), row);
return row;
}
private void prepare(SubRequest request, MOTableCellInfo cell,
MOMutableColumn mcol, R row, boolean creation) {
if (moChangeListeners != null) {
MOChangeEvent changeEvent =
new MOChangeEvent(this, new CellProxy(cell),
cell.getCellOID(),
(creation) ? null : row.getValue(cell.getColumn()),
request.getVariableBinding().getVariable(),
true);
fireBeforePrepareMOChange(changeEvent);
if (changeEvent.getDenyReason() != PDU.noError) {
request.getStatus().setErrorStatus(changeEvent.getDenyReason());
}
}
ChangeSet changeSet = getPendingChangeSet(request, cell.getIndex());
if (changeSet == null) {
changeSet = addPendingChanges(request, row, creation);
}
if ((moTableRowListeners != null) && (!request.hasError())) {
if (isChangeSetComplete(request, row.getIndex(), cell.getColumn())) {
if (logger.isDebugEnabled()) {
logger.debug("Change set complete column="+cell.getColumn()+",rowIndex="+row.getIndex()+",request="+request);
}
MOTableRowEvent rowEvent =
new MOTableRowEvent(this, this, row, changeSet, (creation) ?
MOTableRowEvent.CREATE :MOTableRowEvent.CHANGE,
true);
fireRowChanged(rowEvent);
if (rowEvent.getVetoStatus() != PDU.noError) {
if (rowEvent.getVetoColumn() >= 0) {
int colID = columns[rowEvent.getVetoColumn()].getColumnID();
OID prefix = new OID(getOID());
prefix.append(colID);
SubRequest r = request.getRequest().find(prefix);
if (r != null) {
r.getStatus().setErrorStatus(rowEvent.getVetoStatus());
}
else {
request.getRequest().setErrorStatus(rowEvent.getVetoStatus());
}
}
else {
request.getRequest().setErrorStatus(rowEvent.getVetoStatus());
}
}
}
else if (logger.isDebugEnabled()) {
logger.debug("Change set not yet complete on column="+cell.getColumn()+",rowIndex="+row.getIndex()+
",request="+request);
}
}
if (request.getStatus().getErrorStatus() == PDU.noError) {
mcol.prepare(request, row, changeSet, cell.getColumn());
MOChangeEvent changeEvent =
new MOChangeEvent(this, new CellProxy(cell),
cell.getCellOID(),
row.getValue(cell.getColumn()),
request.getVariableBinding().getVariable(),
true);
fireAfterPrepareMOChange(changeEvent);
if (changeEvent.getDenyReason() != PDU.noError) {
request.getStatus().setErrorStatus(changeEvent.getDenyReason());
}
else {
if ((row instanceof MOMutableRow2PC) &&
(isChangeSetComplete(request, row.getIndex(), cell.getColumn()))) {
((MOMutableRow2PC)row).prepareRow(request, changeSet);
}
}
}
}
@SuppressWarnings("unchecked")
protected int getChangesFromRequest(OID index,
MOTableRow row,
SubRequest request,
Variable[] values,
boolean setDefaultValues,
boolean newRow) {
int lastChangedColumn = -1;
// assign default values
if (setDefaultValues) {
for (int i = 0; (i < values.length) && (i < getColumnCount()); i++) {
if (columns[i] instanceof MOMutableColumn) {
values[i] = ((MOMutableColumn) columns[i]).getDefaultValue();
}
}
}
Request req = request.getRequest();
for (Iterator it = req.iterator(); it.hasNext();) {
SubRequest sreq = it.next();
OID id = sreq.getVariableBinding().getOid();
MOTableCellInfo cellInfo = getCellInfo(id);
if (index.equals(cellInfo.getIndex())) {
int col = cellInfo.getColumn();
if ((col >= 0) && (col < values.length)) {
Variable v = sreq.getVariableBinding().getVariable();
// check that value is really changed
if ((v != null) &&
((row == null) || (newRow) ||
(row.size() <= col) ||
(!v.equals(row.getValue(col))))) {
values[col] = v;
lastChangedColumn = col;
}
}
}
}
return lastChangedColumn;
}
protected boolean hasNewRows(Request key) {
return ((newRows != null) && (newRows.get(key) != null));
}
protected Map getNewRows(Request key) {
if (newRows == null) {
newRows = new WeakHashMap>(4);
}
Map rowMap = newRows.get(key);
if (rowMap == null) {
rowMap = new HashMap(5);
newRows.put(key, rowMap);
}
return rowMap;
}
protected synchronized boolean isChangeSetComplete(SubRequest subRequest,
OID index,
int column) {
ChangeSet changeSet = getPendingChangeSet(subRequest, index);
return (changeSet == null) || (changeSet.getLastChangedColumn() == column);
}
/*
protected synchronized ChangeSet addPendingChange(SubRequest subRequest,
OID index, int column,
Variable value) {
if (pendingChanges == null) {
pendingChanges = new WeakHashMap(4);
}
Map rowMap = (Map) pendingChanges.get(subRequest.getRequest());
if (rowMap == null) {
rowMap = new HashMap(5);
pendingChanges.put(subRequest.getRequest(), rowMap);
}
ChangeSet changeSet = (ChangeSet) rowMap.get(index);
if (changeSet == null) {
changeSet = new ChangeSet(index, new Variable[getColumnCount()]);
}
changeSet.setValue(column, value);
return changeSet;
}
*/
protected synchronized ChangeSet addPendingChanges(SubRequest subRequest,
MOTableRow row,
boolean newRow) {
if (pendingChanges == null) {
pendingChanges = new WeakHashMap>(4);
}
Map rowMap = pendingChanges.get(subRequest.getRequest());
if (rowMap == null) {
rowMap = new HashMap(5);
pendingChanges.put(subRequest.getRequest(), rowMap);
}
Variable[] values = new Variable[getColumnCount()];
int lastChangedColumn =
getChangesFromRequest(row.getIndex(), row, subRequest,
values, newRow, newRow);
ChangeSet changeSet = new ChangeSet(row.getIndex(), values);
changeSet.lastChangedColumn = lastChangedColumn;
rowMap.put(row.getIndex(), changeSet);
return changeSet;
}
protected ChangeSet getPendingChangeSet(SubRequest subRequest,
OID index) {
if (pendingChanges != null) {
Map rowMap = pendingChanges.get(subRequest.getRequest());
if (rowMap != null) {
return rowMap.get(index);
}
}
return null;
}
public void cleanup(SubRequest request) {
OID cellOID = request.getVariableBinding().getOid();
MOTableCellInfo cell = getCellInfo(cellOID);
if ((cell.getIndex() == null) || (cell.getColumn() < 0)) {
return;
}
MOColumn col = getColumn(cell.getColumn());
if (logger.isDebugEnabled()) {
logger.debug("Cleaning-up sub-request ("+
request.getVariableBinding()+") for column: "+col);
}
MOMutableTableRow row = (MOMutableTableRow) model.getRow(cell.getIndex());
if ((row != null) && (col instanceof MOMutableColumn)) {
((MOMutableColumn) col).cleanup(request, row, cell.getColumn());
}
if ((row instanceof MOMutableRow2PC) &&
isChangeSetComplete(request, row.getIndex(), cell.getColumn())) {
((MOMutableRow2PC)row).cleanupRow(request,
getPendingChangeSet(request,
row.getIndex()));
}
request.completed();
}
public void undo(SubRequest request) {
OID cellOID = request.getVariableBinding().getOid();
MOTableCellInfo cell = getCellInfo(cellOID);
MOMutableColumn col = (MOMutableColumn) getColumn(cell.getColumn());
if (logger.isDebugEnabled()) {
logger.debug("Undoing sub-request ("+
request.getVariableBinding()+") for column: "+col);
}
if (hasNewRows(request.getRequest())) {
((MOMutableTableModel)model).removeRow(cell.getIndex());
}
else {
MOMutableTableRow row = (MOMutableTableRow) model.getRow(cell.getIndex());
if (row != null) {
col.undo(request, row, cell.getColumn());
}
if ((row instanceof MOMutableRow2PC) &&
isChangeSetComplete(request, row.getIndex(), cell.getColumn())) {
((MOMutableRow2PC)row).undoRow(request, getPendingChangeSet(request, row.getIndex()));
}
}
}
public OID getOID() {
return oid;
}
public void setModel(M model) {
this.model = model;
}
public void setVolatile(boolean isVolatile) {
this.isVolatile = isVolatile;
}
public M getModel() {
return model;
}
public C[] getColumns() {
return columns;
}
public MOTableIndex getIndexDef() {
return indexDef;
}
public boolean isVolatile() {
return isVolatile;
}
public OID getLowerBound() {
return oid;
}
public OID getUpperBound() {
OID upperBound = new OID(oid);
int lastID = oid.size()-1;
/**
* This is not quite correct because we would have to search up the tree
* if the last sub ID is 0xFFFFFFFF, but since a table OID must end on 1
* by SMI rules we should be on the safe side here.
*/
upperBound.set(lastID, oid.get(lastID)+1);
return upperBound;
}
public boolean isLowerIncluded() {
return false;
}
public boolean isUpperIncluded() {
return false;
}
public boolean isCovered(MOScope other) {
return DefaultMOScope.covers(this, other);
}
public boolean isOverlapping(MOScope other) {
return DefaultMOScope.overlaps(this, other);
}
public synchronized void addMOChangeListener(MOChangeListener l) {
if (moChangeListeners == null) {
moChangeListeners = new Vector(2);
}
moChangeListeners.add(l);
}
public synchronized void removeMOChangeListener(MOChangeListener l) {
if (moChangeListeners != null) {
moChangeListeners.remove(l);
}
}
protected void fireBeforePrepareMOChange(MOChangeEvent changeEvent) {
if (moChangeListeners != null) {
Vector listeners = moChangeListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
(listeners.elementAt(i)).beforePrepareMOChange(changeEvent);
}
}
}
protected void fireAfterPrepareMOChange(MOChangeEvent changeEvent) {
if (moChangeListeners != null) {
Vector listeners = moChangeListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
(listeners.elementAt(i)).afterPrepareMOChange(changeEvent);
}
}
}
protected void fireBeforeMOChange(MOChangeEvent changeEvent) {
if (moChangeListeners != null) {
Vector listeners = moChangeListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
(listeners.elementAt(i)).beforeMOChange(changeEvent);
}
}
}
protected void fireAfterMOChange(MOChangeEvent changeEvent) {
if (moChangeListeners != null) {
Vector listeners = moChangeListeners;
int count = listeners.size();
for (int i = 0; i < count; i++) {
(listeners.elementAt(i)).afterMOChange(changeEvent);
}
}
}
public synchronized void addMOTableRowListener(MOTableRowListener l) {
if (moTableRowListeners == null) {
moTableRowListeners = new Vector>(2);
}
moTableRowListeners.add(l);
}
public synchronized void removeMOTableRowListener(MOTableRowListener l) {
if (moTableRowListeners != null) {
moTableRowListeners.remove(l);
}
}
protected void fireRowChanged(MOTableRowEvent event) {
if (moTableRowListeners != null) {
Vector> listeners = moTableRowListeners;
for (MOTableRowListener listener : listeners) {
listener.rowChanged(event);
}
}
}
public static class ChangeSet implements MOTableRow {
private OID index;
private Variable[] values;
private int lastChangedColumn = -1;
public ChangeSet(OID index, Variable[] values) {
this.index = index;
this.values = values;
}
public OID getIndex() {
return index;
}
public int getLastChangedColumn() {
return lastChangedColumn;
}
public void setValue(int column, Variable value) {
values[column] = value;
this.lastChangedColumn = column;
}
public Variable getValue(int column) {
return values[column];
}
public MOTableRow getBaseRow() {
return null;
}
public int size() {
return values.length;
}
public void setBaseRow(MOTableRow baseRow) {
throw new UnsupportedOperationException();
}
}
class CellProxy implements ManagedObject {
private MOTableCellInfo cellInfo;
private MOScope scope;
public CellProxy(MOTableCellInfo cellInfo) {
this.cellInfo = cellInfo;
this.scope = new OIDScope(cellInfo.getCellOID());
}
public MOScope getScope() {
return scope;
}
public OID find(MOScope range) {
if (range.isCovered(scope)) {
return cellInfo.getCellOID();
}
return null;
}
public void get(SubRequest request) {
DefaultMOTable.this.get(request);
}
public boolean next(SubRequest request) {
return DefaultMOTable.this.next(request);
}
public void prepare(SubRequest request) {
DefaultMOTable.this.prepare(request);
}
public void commit(SubRequest request) {
DefaultMOTable.this.commit(request);
}
public void undo(SubRequest request) {
DefaultMOTable.this.undo(request);
}
public void cleanup(SubRequest request) {
DefaultMOTable.this.cleanup(request);
}
public MOTable getTable() {
return DefaultMOTable.this;
}
}
static class CellInfo implements MOTableCellInfo {
private OID index;
private int id = 0;
private int col = -1;
private MOTableRow row;
private DefaultMOTable table;
public CellInfo(DefaultMOTable table, OID oid) {
this.table = table;
this.index = table.getIndexPart(oid);
if ((oid.size() > table.oid.size()) &&
(oid.startsWith(table.oid))) {
id = oid.get(table.oid.size());
}
}
public CellInfo(DefaultMOTable table, OID index, int column, int columnID) {
this.table = table;
this.index = index;
this.col = column;
this.id = columnID;
}
public CellInfo(DefaultMOTable table, OID index, int column, int columnID, MOTableRow row) {
this(table, index, column, columnID);
this.row = row;
}
public OID getIndex() {
return index;
}
public int getColumn() {
if (col < 0) {
col = table.getColumnIndex(id);
}
return col;
}
public int getColumnID() {
return id;
}
public OID getCellOID() {
return table.getCellOID(index, col);
}
public MOTableRow getRow() {
return row;
}
@Override
public String toString() {
return "CellInfo{" +
"index=" + index +
", id=" + id +
", col=" + col +
", row=" + row +
", table=" + table +
'}';
}
}
public OID getID() {
return getLowerBound();
}
@SuppressWarnings("unchecked")
@Override
public void load(MOInput input) throws IOException {
if (input.getImportMode() == ImportModes.REPLACE_CREATE) {
int count = removeAll();
if (logger.isDebugEnabled()) {
logger.debug("Removed "+count+" rows from "+getID()+
" because importing with a REPLACE import mode");
}
}
Sequence seq = input.readSequence();
for (int i=0; i(this, this, row, MOTableRowEvent.CREATE));
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded row "+row+" into table "+getOID());
}
addRow(row);
}
}
}
@SuppressWarnings("unchecked")
@Override
public void save(MOOutput output) throws IOException {
List rowsToSave = new LinkedList();
synchronized (model) {
for (Iterator it = model.iterator(); it.hasNext(); ) {
R row = it.next();
boolean volatileRow = false;
for (int i = 0; i < columns.length; i++) {
if (columns[i].isVolatile(row, i)) {
volatileRow = true;
break;
}
}
if (!volatileRow) {
Variable[] values = getPersistentValues(row);
IndexedVariables rowValues =
new IndexedVariables(row.getIndex(), values);
rowsToSave.add(rowValues);
}
}
}
Sequence group = new Sequence(rowsToSave.size());
output.writeSequence(group);
for (IndexedVariables rowValues : rowsToSave) {
output.writeIndexedVariables(rowValues);
}
}
/**
* Gets the values of a row that need to be made persistent on behalf of
* a {@link #save(MOOutput output)} call.
*
* @param row
* a MOTableRow that is being saved into a MOOutput stream.
* @return
* an array of Variable
instances that need to be saved.
* Typically, these are all columns of the row - including hidden
* extension columns/values.
* @since 1.2
*/
protected Variable[] getPersistentValues(MOTableRow row) {
Variable[] values = new Variable[row.size()];
for (int i=0; ((i < values.length) && (i < getColumnCount())); i++) {
MOColumn column = getColumn(i);
values[i] = column.getStoreValue(row, i);
}
return values;
}
public Variable[] getDefaultValues() {
Variable[] values = new Variable[getColumnCount()];
for (int i = 0; (i < values.length); i++) {
if (columns[i] instanceof MOMutableColumn) {
values[i] = ((MOMutableColumn) columns[i]).getDefaultValue();
}
}
return values;
}
public String toString() {
return "DefaultMOTable[id="+getID()+",index="+getIndexDef()+",columns="+
Arrays.asList(getColumns())+"]";
}
public boolean covers(OID oid) {
return isCovered(new DefaultMOScope(oid, true, oid, true));
}
public boolean setValue(VariableBinding newValueAndInstancceOID) {
MOTableCellInfo cell = getCellInfo(newValueAndInstancceOID.getOid());
if (cell != null) {
MOColumn col = getColumn(cell.getColumn());
if (logger.isDebugEnabled()) {
logger.debug("Setting value " +
newValueAndInstancceOID + " for column: " + col);
}
// Make sure changes are atomic -> sync whole table model
MOTableRow row;
synchronized (model) {
row = model.getRow(cell.getIndex());
if (row instanceof MOMutableTableRow) {
((MOMutableTableRow)row).setValue(cell.getColumn(),
newValueAndInstancceOID.getVariable());
return true;
}
}
}
return false;
}
private static class RowCacheEntry {
private MOTableRow row;
private OID searchLowerBound;
private boolean searchLowerBoundIncluded;
RowCacheEntry(MOTableRow row,
OID searchLowerBound, boolean searchLowerBoundIncluded) {
this.row = row;
this.searchLowerBound = searchLowerBound;
this.searchLowerBoundIncluded = searchLowerBoundIncluded;
}
}
}