Costins Trivial User and Group Challenge -
06-24-2003
, 11:32 AM
Since I didn't want to buy a new 40 inch monitor just for
the threaded view of my newsreader, I thought, I could
start a new thread.
I fully agree with poet Bernd's last posting:
Object databases are made for developers that want to work
with object representations. If you don't want to load your
data as objects, then SQL may be the better tool for you.
Anyhow, I think I can prove some advantages of object
databases over SQL, if Costin provides a complete, working
and testable solution in Java, so here gos:
Before you get bored reading the code in this posting, you
can download a runnable solution with all the Java sources
that I describe below:
http://www.db4o.com/cc/cc.zip
The 220 kB Zip file includes all the sources listed below, a
db4o database engine and a run.bat batch file to execute
the testcase.
All you will need is a Java runtime environment and you are
set to play with the code.
To repeat the challenge:
--------------------------
The Object Model *must* support a mechanism for the client code to do
the following operations (presumably in a single API calls):
1. verify if a user belongs to a group
2. list all the users in a group
3. list all the groups for a user
4. add a user
5. add a group
6. add a user to a group
6. remove a user form the database
7. remove a group from the database
8. remove a user from a group
Although a good object oriented program would work with objects,
let's define the interface with strings, to be kind to Costin,
so he can execute SQL only, without having to create objects:
public interface CostinsCase {
public boolean userBelongsToGroup(String userName, String groupName);
public void listAllUsersOfGroup(String groupName);
public void listAllGroupsForUser(String userName);
public void addUser(String userName);
public void addGroup(String groupName);
public void addUserToGroup(String userName, String groupName);
public void deleteUser(String userName);
public void deleteGroup(String groupName);
public void removeUserFromGroup(String userName, String groupName);
public void addExistingUserToGroup(String userName, String groupName);
}
To be able to adopt the testcase for multiple database engines,
it's a good idea to set up an interface to init, open and close
the database engine. A cool object database engine would also
come with a proprietary Collection factory, to optimize
query and load performance, so we will have to include that too:
import java.util.*;
public interface Solution extends CostinsCase {
public void initDB();
public void openDB();
public void closeDB();
public Collection createCollection();
}
The class model is trivial, as Costin states and that's right.
We don't even need IDs. Let's let the Group create it's
Collection automatically. I have used a static Solution
object in the TestCase class, to be able to have a factory
callback. It's not the nicest design, but it's the simplest.
The methods listUsers(), and toString() are only there for
convenient printing:
import java.util.*;
public class Group {
private String groupName;
private Collection users;
public Group() {
this.users = TestCase.solution.createCollection();
}
public Group(String groupName) {
this();
this.groupName = groupName;
}
public void listUsers() {
Iterator i = users.iterator();
while (i.hasNext()) {
Object obj = i.next();
if (obj != null) {
System.out.println(obj);
}
}
}
public String toString() {
return "Group " + groupName;
}
public void addUser(User user) {
users.add(user);
}
public void removeUser(User user) {
users.remove(user);
}
}
Overriding the equals() method in the User class allows us
to use Collection#contains():
public class User {
String userName;
public User() {
}
public User(String userName) {
this.userName = userName;
}
public String toString() {
return "User " + userName;
}
public boolean equals(Object obj) {
return obj instanceof User && this.userName.equals(((User) obj).userName);
}
}
Let's set up a generic test case, to see if the program does
what it should:
public class TestCase {
public static Solution solution;
public static void main(String[] args) {
solution = new SolutionDb4o();
solution.initDB();
solution.openDB();
solution.addGroup("ODBMS");
solution.addGroup("RDBMS");
addODBMS("Adrian");
addODBMS("Carl");
addODBMS("Paul");
addRDBMS("Alfredo");
addRDBMS("Bob");
addRDBMS("Costin");
solution.addUser("Scott");
solution.addUserToGroup("Scott", "ODBMS");
solution.addExistingUserToGroup("Scott", "RDBMS");
listGroupUsers("ODBMS");
listGroupUsers("RDBMS");
listUsersGroups("Scott");
ensure(solution.userBelongsToGroup("Scott", "ODBMS"));
ensure(solution.userBelongsToGroup("Scott", "RDBMS"));
ensure(!solution.userBelongsToGroup("Costin", "ODBMS"));
println("\nDeleting User Costin");
solution.deleteUser("Costin");
listGroupUsers("RDBMS");
println("Deleting Group RDBMS");
solution.deleteGroup("RDBMS");
listUsersGroups("Scott");
println("Removing Scott from ODMBS");
solution.removeUserFromGroup("Scott", "ODBMS");
ensure(!solution.userBelongsToGroup("Scott", "ODBMS"));
println("");
listGroupUsers("ODBMS");
solution.closeDB();
}
private static void ensure(boolean condition) {
if (condition) {
println("Pass");
} else {
println("BUG");
}
}
private static void addODBMS(String userName) {
solution.addUser(userName);
solution.addUserToGroup(userName, "ODBMS");
}
private static void addRDBMS(String userName) {
solution.addUser(userName);
solution.addUserToGroup(userName, "RDBMS");
}
private static void listGroupUsers(String groupName) {
println("All Users of Group " + groupName);
solution.listAllUsersOfGroup(groupName);
println("");
}
private static void listUsersGroups(String userName) {
println("All Groups for user " + userName);
solution.listAllGroupsForUser(userName);
println("");
}
private static void println(String str) {
System.out.println(str);
}
}
The above only is the frame and it's reusable for Costin
or anyone else that wants to come up with a solution.
Here is mine:
import java.io.*;
import java.util.*;
import com.db4o.*;
import com.db4o.query.*;
public class SolutionDb4o implements Solution {
private static final String DBFILE = "costinscase.yap";
private ObjectContainer objectContainer;
public boolean userBelongsToGroup(String userName, String groupName) {
Query q = objectContainer.query();
q.constrain(Group.class);
q.descend("groupName").constrain(groupName);
q.descend("users").descend("userName").constrain(u serName);
return (q.execute().size() > 0);
}
public void listAllUsersOfGroup(String groupName) {
queryForGroup(groupName).listUsers();
}
public void listAllGroupsForUser(String userName) {
Query q = objectContainer.query();
q.constrain(Group.class);
q.descend("users").descend("userName").constrain(u serName);
ObjectSet os = q.execute();
while (os.hasNext()) {
System.out.println(os.next());
}
}
public void addUser(String userName) {
objectContainer.set(new User(userName));
objectContainer.commit();
}
public void addGroup(String groupName) {
objectContainer.set(new Group(groupName));
objectContainer.commit();
}
public void addUserToGroup(String userName, String groupName) {
Group group = queryForGroup(groupName);
group.addUser(new User(userName));
objectContainer.set(group);
objectContainer.commit();
}
public void deleteUser(String userName) {
objectContainer.delete(queryForUser(userName));
objectContainer.commit();
}
public void deleteGroup(String groupName) {
Group group = queryForGroup(groupName);
objectContainer.delete(group);
objectContainer.commit();
}
public void removeUserFromGroup(String userName, String groupName) {
Group group = queryForGroup(groupName);
group.removeUser(queryForUser(userName));
objectContainer.set(group);
objectContainer.commit();
}
public void addExistingUserToGroup(String userName, String groupName) {
Group group = queryForGroup(groupName);
group.addUser(queryForUser(userName));
objectContainer.set(group);
objectContainer.commit();
}
public void initDB() {
new File(DBFILE).delete();
}
public void openDB() {
Db4o.configure().objectClass(Group.class.getName() ).cascadeOnUpdate(true);
objectContainer = Db4o.openFile("costinscase.yap");
}
public void closeDB() {
objectContainer.close();
}
public Collection createCollection() {
return new ArrayList();
}
private Group queryForGroup(String groupName) {
Query q = objectContainer.query();
q.constrain(Group.class);
q.descend("groupName").constrain(groupName);
return (Group) refresh(q.execute().next());
}
private User queryForUser(String userName) {
Query q = objectContainer.query();
q.constrain(User.class);
q.descend("userName").constrain(userName);
return (User) refresh(q.execute().next());
}
private Object refresh(Object obj) {
objectContainer.deactivate(obj, Integer.MAX_VALUE);
objectContainer.activate(obj, Integer.MAX_VALUE);
return obj;
}
}
After building the framework, my solution took me about
10 minutes to write.
The SolutionDb4o class contains a total of 93 lines or
2580 characters.
Costin, I challenge you to beat that.
I am looking forward to see a complete working solution
from you in the next hour.
Kind regards,
Carl
--
Carl Rosenberger
db4o - database for objects - http://www.db4o.com |