Design
Designing an ACDP database involves coding subclasses for theCustomDatabase
and the
CustomTable
classes, and optionally for the
SimpleType
class if there is a need for one or more custom
column types.
Furthermore, data encryption and decryption can be enabled by providing an
implementation of the ICipherFactory
interface.
The remaining sections of this page are
A Sample World
In an educational institution students attend courses and teachers instruct courses. You can contact a student and a teacher using their contact details. The following UML diagram, created with the UMLet tool, shows the entities, their attributes and the associations.The black diamond () indicates a composition. Since the contact details are rather complex, by dedicating them their own entity, we avoid the repetition of contact related attributes in the Student and Teacher entities, including any code for checking the validity of values, for example whether a given value is a valid phone number.
Coding Table Classes
Translating the world depicted in the UML diagram above into a collection of ACDP database tables is straightforward: Each entity has a direct correspondence to an ACDP table and a link from one entity to the same or another entity corresponds to either a reference column type or an array of references column type, depending on the cardinality of the link.For each table class we need to define the columns of the table as well as the characteristic sequence of columns, also known as the table definition:
- Create a
Column
instance with theCL
column factory class for each column of the table and assign each column instance to a separate variable. - Define a constructor and invoke the
initialize
method of the super class with the column instances declared in the first step.
import static acdp.design.ST.Nulls.NO_NULL; import static acdp.design.ST.Nulls.NULLABLE; import static acdp.design.ST.OutrowStringLength.SMALL; final class CourseTable extends CustomTable { static final Column<String> ID = CL.ofString(NO_NULL, SMALL); static final Column<String> NAME = CL.ofString(NO_NULL, SMALL); static final Column<Ref> TEACHER = CL.ofRef(); CourseTable() { initialize(ID, NAME, TEACHER); } } final class StudentTable extends CustomTable { static final Column<Integer> STUDENT_NUMBER = CL.ofInteger(NO_NULL); static final Column<String> NAME = CL.ofString(NO_NULL, SMALL); static final Column<Ref> CONTACT = CL.ofRef(); static final Column<Ref[]> COURSES = CL.ofArrayOfRef(30); StudentTable() { initialize(STUDENT_NUMBER, NAME, CONTACT, COURSES); } } final class TeacherTable extends CustomTable { static final Column<String> NAME = CL.ofString(NO_NULL, SMALL); static final Column<Float> SALARY = CL.ofFloat(NULLABLE); static final Column<Ref> CONTACT = CL.ofRef(); static final Column<Ref[]> COURSES = CL.ofArrayOfRef(45); TeacherTable() { initialize(NAME, SALARY, CONTACT, COURSES); } } final class ContactTable extends CustomTable { static final Column<String> PHONE_NUMBER = CL.ofString( Charset.forName("US-ASCII"), NULLABLE, 12); static final Column<String> EMAIL = CL.ofString(NO_NULL, SMALL); static final Column<String> STREET = CL.ofString(NO_NULL, SMALL); static final Column<String> CITY = CL.ofString(NO_NULL, SMALL); static final Column<String> STATE = CL.ofString(NO_NULL, SMALL); static final Column<Integer> POSTAL_CODE = CL.ofInteger(NO_NULL); static final Column<String> COUNTRY = CL.ofString(NULLABLE, SMALL); ContactTable() { initialize(PHONE_NUMBER, EMAIL, STREET, CITY, STATE, POSTAL_CODE, COUNTRY); } }Remarks:
-
The column variable declarations include the
static
modifier. This is possible because we do not intend to reuse any of the table classes in our database or some other database. (See the description of theCustomTable
class for details.) -
Always include the
final
modifier in the declaration of a column variable. Here, we included thefinal
modifier in the declaration of the table classes too. A more elaborated table class may be derived from a more general (abstract) table super class. -
Note that we have not yet assigned names to the tables and columns.
We have also not yet specified the target tables of the reference column
types that appear in the table classes Courses, Student and Teacher.
This will be done later in Chapter
Creation
when we prepare the table classes for processing by ACDP's Setup Tool. -
The
CL
column factory class provides factory methods for columns with a type corresponding to a built-in Java type, including arrays. TheNO_NULL
orNULLABLE
argument creates a column type that prohibits or allows thenull
value to be stored in the column. Thenull
value is always allowed as a value in a column of an array, a reference or an array of references column type. -
Invoking
CL.ofString()
returns a column with a nullable string column type of a length comparable to the length of an ordinary JavaString
instance. -
A column of a string column type created with the
SMALL
argument allows for strings with a length of 255 characters at most. -
The
PHONE_NUMBER
column in the Contact table is created by an invocation ofCL.ofString(Charset.forName("US-ASCII"), NULLABLE, 12)
. This allows for a phone number with a length of 12 characters at most. The really special thing about this column type, however, is its so called inrow storage scheme which allows the particularly efficient storage of values. - A student can attend at most 30 courses and a teacher instructs at most 45 courses.
CL.ofArray(int, SimpleType)
method with
any built-in or custom non-array element type.
For example, CL.ofArray(73, ST.beDouble(NO_NULL))
creates an
array with a maximum of 73 Double
values that are not allowed
to be null
.
(The ST
class is a factory class for ACDP's built-in non-array column types.)
Coding the Database Class
Coding the database class includes defining the tables as well as a constructor which, when invoked, opens the database.final class EduDB extends CustomDatabase { static final CourseTable COURSE_TABLE = new CourseTable(); static final StudentTable STUDENT_TABLE = new StudentTable(); static final TeacherTable TEACHER_TABLE = new TeacherTable(); static final ContactTable CONTACT_TABLE = new ContactTable(); EduDB(Path mainFile, int opMode, boolean writeProtect, int consistencyNumber) { open(mainFile, opMode, writeProtect, null, consistencyNumber, COURSE_TABLE, STUDENT_TABLE, TEACHER_TABLE, CONTACT_TABLE); } }Remarks:
-
The table variable declarations include the
static
modifier. This is possible because we do not intend to reuse any of the table classes in our or some other database. (See the description of theCustomDatabase
class for details.) -
Always include the
final
modifier in the declaration of a table variable. Here, we included thefinal
modifier in the declaration of the database class too. A more elaborated database class may be derived from a more general (abstract) database super class. -
Note that we have not yet assigned a name to the database.
This will be done later in Chapter
Creation
when we prepare the database class for processing by ACDP's Setup Tool. -
The arguments of the
open
method are explained in ChapterOpening
.
Coding a Custom Column Type
Suppose we want to store the salary of a teacher as a value of typejava.math.BigInteger
rather than java.lang.Float
.
Since ACDP has no such built-in type, we must code our own
BigInteger
column type.
All non-array column types are derived from the
SimpleType
class.
A class that extends the SimpleType
class must implement the
toBytes(T)
and the
fromBytes(byte[], int, int)
methods which
convert a value of type T
, for example BigInteger
,
into a byte array and vice versa.
Moreover, implementers of a custom column type must implement a
public
and static
factory method annotated
with the
TypeFromDesc
annotation which creates an instance of the
column type from its so called type descriptor.
The type descriptor is a String
that uniquely identifies the
column type.
It contains enough information so that an instance of the column type can be
created from it.
BigInteger
column type looks as follows:
public final class BigIntegerType extends SimpleType<BigInteger> { private static final String TD_PREFIX = "BigInteger"; @TypeFromDesc public static final BigIntegerType createType(String typeDesc) throws CreationException { final TypeDesc td = new TypeDesc(typeDesc); if (!td.prefix.equals(TD_PREFIX) || td.scheme != Scheme.INROW || !td.variable) { throw new CreationException("Illegal type descriptor"); } return new BigIntegerType(td.limit, Nulls.get(td.nullable)); } BigIntegerType(int limit, Nulls nulls) throws IllegalArgumentException { super(BigInteger.class, Scheme.INROW, nulls.value(), limit, true); } @Override protected final String typeDescPrefix() { return TD_PREFIX; } @Override protected final byte[] toBytes(BigInteger val) throws NullPointerException { return val.toByteArray(); } @Override public final BigInteger fromBytes(byte[] bytes, int offset, int len) throws IndexOutOfBoundsException { return new BigInteger(Arrays.copyOfRange(bytes, offset, offset + len)); } }Remarks:
-
The
limit
parameter of the constructor lets us create columns wich accept integer values of an arbitrary size. Recall that Java'sint
andlong
types are 4 and 8 bytes in size, respectively. -
The
nulls
parameter of the constructor specifies whether the column type allows thenull
value. -
The second and fifth arguments of the super constructor invocation tell
us that the
BigInteger
column type uses the efficient inrow storage scheme and expects the byte array returned by thetoBytes
method to be of a variable length. -
The type descriptor starts with a prefix which by default starts
with the uppercase letter
L
followed by the qualified name of the column type's class name. Overriding thetypeDescPrefix()
method lets you specify your own prefix. Such a custom prefix must start with an uppercase letter because the type descriptor of a custom column type must start with an uppercase letter. -
Note that the
createType(String)
factory method throws aCreationException
if the argument is not a valid type descriptor for theBigInteger
column type.
BigInteger
custom column type to replace
the Float
built-in column type in the Salary column of the
Teacher table.
Simply replace the line of code
static final Column<Float> SALARY = CL.ofFloat(NULLABLE);
in the TeacherTable
class with
static final Column<BigInteger> SALARY = CL.create(
new BigIntegerType(20, NULLABLE));
The Salary column is now a column of type BigInteger
with a
maximum size in its two's-complement binary representation of 20 bytes.
The column accepts the null
value.
Coding a Cipher Factory
Data stored in an ACDP database is not encrypted by default. If encryption is required then we need to provide an implementation of theICipherFactory
interface.
Such a class defines two methods: A factory method returning an instance of
Java's javax.crypto.Cipher
class and a method that initializes
the cipher.
That said, the strength of encryption of data stored in an ACDP database
is not a property of ACDP itself but is completely governed by the strength
of encryption the Cipher
instance returned by the
createCipher
method is able to deliver.
Here is a ready-to-play-with cipher factory class which unrealistically discloses the secret key in the source code:
final class CipherFactory implements ICipherFactory { private final IvParameterSpec iv = new IvParameterSpec(new byte[] { 114, -8, 22, -67, -71, 30, 118, -103, 51, -45, -110, -65, 16, -127, -73, 103 }); private final Key key = new SecretKeySpec(new byte[] { 114, -8, 23, -67, -71, 30, 118, -103, 51, -45, -110, -65, 16, -127, -73, 103 }, "AES"); @Override public final Cipher createCipher() throws NoSuchAlgorithmException, NoSuchPaddingException { // Example with padding: AES/CBC/PKCS5Padding return Cipher.getInstance("AES/CTR/NoPadding"); } @Override public final void initCipher(Cipher cipher, boolean encrypt) throws InvalidKeyException, InvalidAlgorithmParameterException { cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, iv); } }When we speak of an ACDP database we usually mean the writable
WRtype. However, as described in Section
Creating an RO Database from a WR Databaseof Chapter
Creation, there exists a second type: the readonly
ROtype. The cipher for a WR database must be a byte-oriented stream cipher or must behave that way. With the
NoPadding
attribute, the createCipher
method of the cipher factory from above returns such a cipher.
The cipher factory can therefore be used for a WR database.
(No such restriction applies for an RO database.)
For details consult the description of the
ICipherFactory
interface.