/*
PermMatrix.java
This class stores a read-only matrix of PermEntry bits, organized by
object type and field id's.
Created: 3 October 1997
Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2014
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.common;
import java.rmi.RemoteException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import arlut.csd.ganymede.rmi.Base;
import arlut.csd.ganymede.rmi.BaseField;
import arlut.csd.ganymede.server.PermissionMatrixDBField;
/*------------------------------------------------------------------------------
class
PermMatrix
------------------------------------------------------------------------------*/
/**
* <p>Immutable, serializable permissions matrix object, used to
* handle permissions for a given user, admin, or role.</p>
*
* <p>This class stores a Map of {@link
* arlut.csd.ganymede.common.PermEntry PermEntry} objects, organized
* by object type and field id's.</p>
*
* <p>The keys to the Map are Strings that are encoded by the static
* {@link
* arlut.csd.ganymede.server.PermissionMatrixDBField#matrixEntry(short,
* short) matrixEntry()} methods defined in this class. I probably
* could have used some sort of class object for the key, but then I
* would have had to define a key() of similar complexity to the
* matrixEntry() and decode methods anyway, as well as some sort of
* on-disk representation for the Ganymede.db file.</p>
*
* <p>Here's some examples of the key encoding algorithm:</p>
*
* <ul>
* <li><code>3::</code> - Object type 3, permission for object itself</li>
* <li><code>3:10</code> - Object type 3, permission for field 10</li>
* </ul>
*
* <p>PermMatrix is used on the client in the Permissions Editor dialog,
* and on the server in both
* {@link arlut.csd.ganymede.server.PermissionMatrixDBField PermissionMatrixDBField}
* and {@link arlut.csd.ganymede.server.GanymedeSession GanymedeSession}.</p>
*/
public final class PermMatrix implements java.io.Serializable {
static final boolean debug = false;
static final long serialVersionUID = -6816094603281289860L;
// ---
private final Hashtable<String, PermEntry> matrix;
/* -- */
public PermMatrix()
{
matrix = new Hashtable<String, PermEntry>();
}
public PermMatrix(Hashtable<String, PermEntry> orig)
{
if (orig == null)
{
this.matrix = new Hashtable<String, PermEntry>();
}
else
{
this.matrix = new Hashtable<String, PermEntry>(orig);
}
}
public PermMatrix(PermMatrix orig)
{
this.matrix = new Hashtable<String, PermEntry>(orig.matrix);
}
/**
* <p>Returns a read-only view of the underlying Hashtable we're
* using to keep our permissions data.</p>
*
* <p>Used by {@link
* arlut.csd.ganymede.server.PermissionMatrixDBField} to provide
* debug dumping of our contents in a server context.</p>
*/
public Map<String, PermEntry> getMatrix()
{
return Collections.unmodifiableMap(matrix);
}
/**
* <p>Returns a copy of the internal Hashtable containing
* our permission matrix map.</p>
*
* <p>Used by {@link arlut.csd.ganymede.server.PermMatrixCkPoint}
* class.</p>
*/
public Hashtable<String, PermEntry> getMatrixClone()
{
return new Hashtable<String, PermEntry>(matrix);
}
/**
* <p>This method combines this PermMatrix with that
* of orig. The returned PermMatrix will allow
* any access either of the source PermMatrices
* would.</p>
*/
public PermMatrix union(Hashtable<String, PermEntry> orig)
{
return this.union(new PermMatrix(orig));
}
/**
* <p>This method returns a PermMatrix that has the combined
* permissions of this PermMatrix and that of orig. The returned
* PermMatrix will allow any access either of the source
* PermMatrices would.</p>
*
* <p>Note that unlike all the other methods in PermMatrix, we are
* handling inheritance of default permissions for an object base
* into fields which do not have permissions specified. We have
* to take this into account here in order to do a proper union.</p>
*
* <p>As a consequence, this union() method is more complex than
* you might expect. That complexity really is needed.. don't
* mess with this unless you really, really know what you're doing.</p>
*/
public synchronized PermMatrix union(PermMatrix orig)
{
PermMatrix result;
PermEntry entry1, entry2, entry3;
/* -- */
if (orig == null)
{
return this;
}
result = new PermMatrix();
for (String key: this.matrix.keySet())
{
entry1 = this.matrix.get(key);
entry2 = orig.matrix.get(key);
if (entry2 != null)
{
result.matrix.put(key, entry1.union(entry2));
}
else
{
// okay, orig didn't have any entry for key
if (!isBasePerm(key))
{
// We are union'ing a field entry.. since orig doesn't
// contain an explicit record for this field while our
// matrix does, see if we can find a record for the
// containing base in field and union that with this entry..
// this will serve to maintain the permission inheritance
// issues
entry3 = orig.matrix.get(baseEntry(key));
// union will handle a null entry3
result.matrix.put(key, entry1.union(entry3));
}
else
{
// we've got a base entry from this.matrix that wasn't in orig,
// so we need to just copy it into result
result.matrix.put(key, entry1);
}
}
}
// result now contains all of the records from this.matrix,
// union'ed with the orig versions. The only problem now is that
// we haven't covered entries in orig that weren't in this.matrix.
// loop over the orig values for completeness
for (String key: orig.matrix.keySet())
{
entry1 = orig.matrix.get(key);
entry2 = this.matrix.get(key);
if (entry2 != null)
{
result.matrix.put(key, entry1.union(entry2));
}
else
{
if (!isBasePerm(key))
{
// the orig matrix has a field entry that we didn't
// have a match for in this.matrix.. we need to check
// to see if we have a base entry for the corresponding
// base that we need to union in to reflect the default
// base -> field inheritance
entry3 = this.matrix.get(baseEntry(key));
// union will handle a null entry3
result.matrix.put(key, entry1.union(entry3));
}
else
{
result.matrix.put(key, entry1);
}
}
}
return result;
}
/**
* <p>Returns a PermEntry object representing this PermMatrix's
* permissions on the field <fieldID> in base <baseID></p>
*
* <p>If there is no entry in this PermMatrix for the given field,
* getPerm() will return null.</p>
*
* @see arlut.csd.ganymede.common.PermMatrix
*/
public PermEntry getPerm(short baseID, short fieldID)
{
return matrix.get(matrixEntry(baseID, fieldID));
}
/**
* <p>Returns a PermEntry object representing this PermMatrix's
* permissions on the base <baseID></p>
*
* @see arlut.csd.ganymede.common.PermMatrix
*/
public PermEntry getPerm(short baseID)
{
return matrix.get(matrixEntry(baseID));
}
/**
* <p>Returns a PermEntry object representing this PermMatrix's
* permissions on the field <field> in base <base></p>
*
* @see arlut.csd.ganymede.common.PermMatrix
*/
public PermEntry getPerm(Base base, BaseField field)
{
try
{
return getPerm(base.getTypeID(), field.getID());
}
catch (RemoteException ex)
{
throw new RuntimeException("caught remote: " + ex);
}
}
/**
* <p>Returns a PermEntry object representing this PermMatrix's
* permissions on the base <base></p>
*
* @see arlut.csd.ganymede.common.PermMatrix
*/
public PermEntry getPerm(Base base)
{
try
{
return matrix.get(matrixEntry(base.getTypeID()));
}
catch (RemoteException ex)
{
throw new RuntimeException("caught remote: " + ex);
}
}
/**
* <p>Private method to generate a key for use in
* our internal Hashtable, used to encode the
* permission for a given {@link arlut.csd.ganymede.server.DBObjectBase
* DBObjectBase} and {@link arlut.csd.ganymede.server.DBObjectBaseField
* DBObjectBaseField}.</p>
*/
private String matrixEntry(short baseID, short fieldID)
{
return (baseID + ":" + fieldID);
}
/**
* <p>Private method to generate a key for use in
* our internal Hashtable, used to encode the
* permission for a given {@link arlut.csd.ganymede.server.DBObjectBase
* DBObjectBase}.</p>
*/
private String matrixEntry(short baseID)
{
return (baseID + "::");
}
/**
* <p>Returns true if the given String encodes the identity of
* a {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} and
* not a field within a DBObjectBase.</p>
*/
private boolean isBasePerm(String matrixEntry)
{
return (matrixEntry.indexOf("::") != -1);
}
/**
* <p>Private helper method used to decode a hash key generated
* by the matrixEntry() methods.</p>
*
* @return Returns the {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase}
* object id encoded by the given String.
*/
private short entryBase(String matrixEntry)
{
if (matrixEntry.indexOf(':') == -1)
{
throw new IllegalArgumentException("not a valid matrixEntry");
}
String baseStr = matrixEntry.substring(0, matrixEntry.indexOf(':'));
try
{
return Short.parseShort(baseStr);
}
catch (NumberFormatException ex)
{
throw new RuntimeException("bad string format:" + ex);
}
}
/**
* <p>Private helper method used to decode a hash key generated
* by the matrixEntry() methods.</p>
*
* @return Returns the
* {@link arlut.csd.ganymede.server.DBObjectBaseField DBObjectBaseField}
* object id encoded by the given String.
*/
private short entryField(String matrixEntry)
{
if (matrixEntry.indexOf(':') == -1)
{
throw new IllegalArgumentException("not a valid matrixEntry");
}
if (isBasePerm(matrixEntry))
{
throw new IllegalArgumentException("not a field matrixEntry");
}
String fieldStr = matrixEntry.substring(matrixEntry.lastIndexOf(':')+1);
try
{
return Short.parseShort(fieldStr);
}
catch (NumberFormatException ex)
{
throw new RuntimeException("bad string format:" + ex);
}
}
/**
* <p>Private helper method used to generate a matrixEntry() encoded String
* for a single {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} from
* a matrixEntry() encoded String that also includes a field specification.</p>
*/
private String baseEntry(String matrixEntry)
{
if (isBasePerm(matrixEntry))
{
return matrixEntry;
}
else
{
return matrixEntry(entryBase(matrixEntry));
}
}
/**
* <p>Debugging output</p>
*/
public String toString()
{
try
{
return PermissionMatrixDBField.debugdecode(matrix);
}
catch (Throwable ex)
{
// the client will get a ClassNotFoundException on PermissionMatrixDBField.
return super.toString();
}
}
public static void main(String argv[])
{
PermMatrix x = new PermMatrix();
x.matrix.put(x.matrixEntry((short) 267), PermEntry.getPermEntry(true, false, false, false));
x.matrix.put(x.matrixEntry((short) 267, (short) 1), PermEntry.getPermEntry(true, false, false, true));
x.matrix.put(x.matrixEntry((short) 267, (short) 2), PermEntry.getPermEntry(true, false, false, true));
x.matrix.put(x.matrixEntry((short) 267, (short) 3), PermEntry.getPermEntry(true, false, false, true));
x.matrix.put(x.matrixEntry((short) 267, (short) 4), PermEntry.getPermEntry(true, true, true, false));
System.err.println("Matrix 1: " + x.toString());
PermMatrix y = new PermMatrix();
y.matrix.put(y.matrixEntry((short) 267), PermEntry.getPermEntry(false, true, false, false));
y.matrix.put(y.matrixEntry((short) 267, (short) 1), PermEntry.getPermEntry(true, false, false, false));
y.matrix.put(y.matrixEntry((short) 267, (short) 2), PermEntry.getPermEntry(true, false, false, false));
y.matrix.put(y.matrixEntry((short) 267, (short) 5), PermEntry.getPermEntry(true, false, false, false));
y.matrix.put(y.matrixEntry((short) 267, (short) 6), PermEntry.getPermEntry(true, false, false, false));
System.err.println("Matrix 2: " + y.toString());
PermMatrix z = x.union(y);
System.err.println("Matrix 1 union 2: " + z.toString());
PermMatrix a = y.union(x);
System.err.println("Matrix 2 union 1: " + a.toString());
}
}