Date: Fri, 29 Mar 2024 13:53:20 +0000 (UTC) Message-ID: <1373107698.25433.1711720400035@ae5f4610bf64> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_25432_917499121.1711720400034" ------=_Part_25432_917499121.1711720400034 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
Here we present a Java implementation of the file system server.
On this page:
Now that we have selected our key and value types, we can generate the m= aps as follows:
$ slice2freezej -I$(ICE_HOME)/slice -I. --ice= --dict \ FilesystemDB.IdentityFileEntryMap,Ice::Identity,\ FilesystemDB::FileEntry \ IdentityFileEntryMap FilesystemDB.ice \ $(ICE_HOME)/slice/Ice/Identity.ice $ slice2freezej -I$(ICE_HOME)/slice -I. --ice --dict \ FilesystemDB.IdentityDirectoryEntryMap,Ice::Identit= y,\ FilesystemDB::DirectoryEntry \ IdentityDirectoryEntryMap FilesystemDB.ice \ $(ICE_HOME)/slice/Ice/Identity.ice
The resulting map classes are named IdentityFileEntryMap
an=
d IdentityDirectoryEntryMap
.
The server's main program is very simple:
import Filesystem.*; import FilesystemDB.*; public class Server extends Ice.Application { public Server(String envName) { _envName =3D envN= ame; } public int run(String[] args) { Ice.ObjectAdapter adap= ter =3D communicator().createObjectAdapter("MapFilesystem"); Freeze.Connection conn= ection =3D null; try { con= nection =3D Freeze.Util.createConnection(communicator(), _envName); Ide= ntityFileEntryMap fileDB =3D new IdentityFileEntryMap(connection, FileI.filesD= B(), true); Ide= ntityDirectoryEntryMap dirDB =3D &nb= sp; new IdentityDirectoryEntryMap(connection, Di= rectoryI.directoriesDB(), true); ada= pter.addDefaultServant(new FileI(communicator(), _envName), = "file"); ada= pter.addDefaultServant(new DirectoryI(communicator(), _envName),&= nbsp;""); ada= pter.activate(); com= municator().waitForShutdown(); } finally { con= nection.close(); } return 0; } public static void main(String[] args) { Server app =3D&nb= sp;new Server("db"); app.main("MapServer", = args, "config.server"); System.exit(0); } private String _envName; }
First, we import the Filesystem
and FilesystemDB packages.
Next, we define the class FilesystemApp
as a subclass of cation
, and provide a constructor taking a=
string argument:
FilesystemApp(const s= tring& envName) : _envName(envName)&nb= sp;{}
The string argument represents the name of the database environment, and=
is saved for later use in run
.
The interesting part of run
are the few lines of code that =
create the database connection and the two maps that store files and direct=
ories, plus the code to add the two default servants:
&n= bsp; connection =3D Freeze.Util.createConnection(comm= unicator(), _envName); Ide= ntityFileEntryMap fileDB =3D &nb= sp; new IdentityFileEntryMap(connection, FileI.f= ilesDB(), true); Ide= ntityDirectoryEntryMap dirDB =3D &nb= sp; new IdentityDirectoryEntryMap(connection, Di= rectoryI.directoriesDB(), true); ada= pter.addDefaultServant(new FileI(communicator(), _envName), = "file"); ada= pter.addDefaultServant(new DirectoryI(communicator(), _envName),&= nbsp;"");
run
keeps the database connection open for the duration of =
the program for performance reasons. As we will see shortly, individual ope=
ration implementations will use their own connections; however, it is subst=
antially cheaper to create second (and subsequent connections) than it is t=
o create the first connection.
For the default servants, we use file
as the category for f=
iles. For directories, we use the empty default category.
FileI
with a Freeze Map in Java=
h1>
The class definition for FileI
is very simple:
public class FileI extends _Fi= leDisp { public FileI(Ice.Communicator communicator, String&nb= sp;envName) { _communicator =3D = ;communicator; _envName =3D envN= ame; } // Slice operations... public static String filesDB() { return "files"; } private void halt(Freeze.DatabaseException e) { java.io.StringWriter s= w =3D new java.io.StringWriter(); java.io.PrintWriter pw= =3D new java.io.PrintWriter(sw); e.printStackTrace(pw); pw.flush(); _communicator.getLogger().e= rror( "fa= tal database error\n" + sw.toString() + "\n***&nbs= p;Halting JVM ***"); Runtime.getRuntime().halt(1= ); } private Ice.Communicator _communicator; private String _envName; }
The FileI
class stores the communicator and the environment=
name. These members are initialized by the constructor. The filesDB<=
/code> static method returns the name of the file map, and the
halt=
code> member function is used to stop the server if it encounters a catastr=
ophic error.
The Slice operations all follow the same implementation strategy: we cre= ate a database connection and the file map and place the body of the operat= ion into an infinite loop:
public String someOperation(/* ... */ Ice.Current&= nbsp;c) { Freeze.Connection conn= ection =3D Freeze.Util.createConnection(_communicator, _envName); try { Ide= ntityFileEntryMap fileDB =3D new IdentityFileEntryMap(connec= tion, filesDB()); for= (;;) { &nb= sp; try { &nb= sp; // Operation impleme= ntation here... &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } } } finally { con= nection.close(); } }
Each operation creates its own database connection and map for concurren=
cy reasons: the database takes care of all the necessary locking, so there =
is no need for any other synchronization in the server. If the database det=
ects a deadlock, the code handles the corresponding DeadlockException=
and simply tries again until the operation eventually succeeds; any=
other database exception indicates that something has gone seriously wrong=
and terminates the server.
Here is the implementation of the name
method:
public String name(Ice.Current c) { Freeze.Connection conn= ection =3D Freeze.Util.createConnection(_communicator, _envName); try { Ide= ntityFileEntryMap fileDB =3D new IdentityFileEntryMap(connec= tion, filesDB()); for= (;;) { &nb= sp; try { &nb= sp; FileEntry entry =3D&= nbsp;fileDB.get(c.id); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; throw&= nbsp;new Ice.ObjectNotExistException(); &nb= sp; } &nb= sp; return entry.name; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } } } finally { con= nection.close(); } }
The implementation could hardly be simpler: the default servant uses the=
identity in the Curren=
t
object to index into the file map. If a record with this ident=
ity exists, it returns the name of the file as stored in the FileEntr=
y
structure in the map. Otherwise, if no such entry exists, it throw=
s ObjectNotExistException
. This happens if the file existed at=
some time in the past but has since been destroyed.
The read
implementation is almost identical. It returns the=
text that is stored by the FileEntry
:
public String[] read(Ice.Current c) { Freeze.Connection conn= ection =3D Fre= eze.Util.createConnection(_communicator, _envName); try { Ide= ntityFileEntryMap fileDB =3D new IdentityFileEntryMap(connec= tion, filesDB()); for= (;;) { &nb= sp; try { &nb= sp; FileEntry entry =3D&= nbsp;fileDB.get(c.id); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; throw&= nbsp;new Ice.ObjectNotExistException(); &nb= sp; } &nb= sp; return entry.text; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } } } finally { con= nection.close(); } }
The write
implementation updates the file contents and call=
s put
on the iterator to update the map with the new contents:=
public void write(String[] text, Ice.Current c) throws GenericError { Freeze.Connection conn= ection =3D Freeze.Util.createConnection(_communicator, _envName); try { Ide= ntityFileEntryMap fileDB =3D new IdentityFileEntryMap(connec= tion, filesDB()); for= (;;) { &nb= sp; try { &nb= sp; FileEntry entry =3D&= nbsp;fileDB.get(c.id); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; throw&= nbsp;new Ice.ObjectNotExistException(); &nb= sp; } &nb= sp; entry.text =3D text; &nb= sp; fileDB.put(c.id, entry); &nb= sp; break; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } } } finally { con= nection.close(); } }
Finally, the destroy
implementation for files must update t=
wo maps: it needs to remove its own entry in the file map as well as update=
the nodes
map in the parent to remove itself from the parent'=
s map of children. This raises a potential problem: if one update succeeds =
but the other one fails, we end up with an inconsistent file system: either=
the parent still has an entry to a non-existent file, or the parent lacks =
an entry to a file that still exists.
To make sure that the two updates happen atomically, destroy
performs them in a transaction:
public void destroy(Ice.Current c) throws PermissionDenie= d { Freeze.Connection conn= ection =3D Freeze.Util.createConnection(_communicator, _envName); try { Ide= ntityFileEntryMap fileDB =3D new IdentityFileEntryMap(connec= tion, filesDB()); Ide= ntityDirectoryEntryMap dirDB =3D &nb= sp; new IdentityDirectoryEntryMap(connection, Di= rectoryI.directoriesDB()); for= (;;) { &nb= sp; Freeze.Transaction txn =3D null; &nb= sp; try { &nb= sp; txn =3D connection.b= eginTransaction(); &nb= sp; FileEntry entry =3D&= nbsp;fileDB.get(c.id); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; throw&= nbsp;new Ice.ObjectNotExistException(); &nb= sp; } &nb= sp; DirectoryEntry dirEntry&n= bsp;=3D (DirectoryEntry)dirDB.get(entry.parent); &nb= sp; if (dirEntry =3D=3D&= nbsp;null) { &nb= sp; halt(n= ew Freeze.DatabaseException( "consistency error: file wi= thout parent")); &nb= sp; } &nb= sp; dirEntry.nodes.remove(entry.na= me); &nb= sp; dirDB.put(entry.parent, d= irEntry); &nb= sp; fileDB.remove(c.id); &nb= sp; txn.commit(); &nb= sp; txn =3D null; &nb= sp; break; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } finally { &nb= sp; if (txn !=3D nu= ll) { &nb= sp; txn.ro= llback(); &nb= sp; } &nb= sp; } } } finally { con= nection.close(); } }
As you can see, the code first establishes a transaction and then locate=
s the file in the parent directory's map of nodes. After removing the file =
from the parent, the code updates the parent's persistent state by calling =
put
on the parent iterator and then removes the file from the =
file map before committing the transaction.
DirectoryI
with a Freeze Map=
in JavaThe DirectoryI.directoriesDB
implementation returns the str=
ing directories
, and the halt
implementation is t=
he same as for FileI
, so we do not show them here.
Turning to the constructor, we must cater for two different scenarios:= p>
This means that the root directory (which must always exist) may or may =
not be present in the database. Accordingly, the constructor looks for the =
root directory (with the fixed identity RootDir
); if the root =
directory does not exist in the database, it creates it:
public DirectoryI(Ice.Communicator communicator, = ;String envName) { _communicator =3D = ;communicator; _envName =3D envN= ame; Freeze.Connection conn= ection =3D Freeze.Util.createConnection(_communicator, _envName); try { Ide= ntityDirectoryEntryMap dirDB =3D new IdentityDirectoryEntryMap(connection, directoriesD= B()); for= (;;) { &nb= sp; try { &nb= sp; Ice.Identity rootId = =3D new Ice.Identity("RootDir", ""); &nb= sp; DirectoryEntry entry = ;=3D dirDB.get(rootId); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; dirDB.= put(rootId, new DirectoryEntry("/", new Ice.Identity("", "")= , null)); &nb= sp; } &nb= sp; break; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } } } finally { con= nection.close(); } }
Next, let us examine the implementation of createDirectory
.=
Similar to the FileI::destroy
operation, createDirector=
y
must update both the parent's nodes map and create a new entry in =
the directory map. These updates must happen atomically, so we perform them=
in a separate transaction:
public DirectoryPrx createDirectory(String name, Ice.Current&= nbsp;c) throws NameInUse { Freeze.Connection conn= ection =3D Freeze.Util.createConnection(_communicator, _envName); try { Ide= ntityDirectoryEntryMap dirDB =3D &nb= sp; new IdentityDirectoryEntryMap(connection, directo= riesDB()); for= (;;) { &nb= sp; Freeze.Transaction txn =3D null; &nb= sp; try { &nb= sp; txn =3D connection.b= eginTransaction(); &nb= sp; DirectoryEntry entry = ;=3D dirDB.get(c.id); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; throw&= nbsp;new Ice.ObjectNotExistException(); &nb= sp; } &nb= sp; if(name.length() =3D=3D&n= bsp;0 || entry.nodes.get(name) !=3D null) { &nb= sp; throw&= nbsp;new NameInUse(name); &nb= sp; } &nb= sp; DirectoryEntry newEntry&n= bsp;=3D new DirectoryEntry(name, c.id, null); &nb= sp; Ice.Identity id =3D&= nbsp;new Ice.Identity(java.util.UUID.randomUUID().toString(), ""); &nb= sp; DirectoryPrx proxy = =3D DirectoryPrxHelper.uncheckedCast(c.adapter.createProxy(id)); &nb= sp; entry.nodes.put(name, new= NodeDesc(name, NodeType.DirType, proxy)); &nb= sp; dirDB.put(c.id, entry); &nb= sp; dirDB.put(id, newEntry); &nb= sp; txn.commit(); &nb= sp; txn =3D null; &nb= sp; return proxy; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } finally { &nb= sp; if(txn !=3D null)&nb= sp;{ &nb= sp; txn.ro= llback(); &nb= sp; } &nb= sp; } } } finally { con= nection.close(); } }
After establishing the transaction, the code ensures that the directory =
does not already contain an entry with the same name and then initializes a=
new DirectoryEntry
, setting the name to the name of the new d=
irectory, and the parent to its own identity. The identity of the new direc=
tory is a UUID, which ensures that all directories have unique identities. =
In addition, the UUID prevents the accidental rebirth of a file or directory in the futu=
re.
The code then initializes a new NodeDesc
structure with the=
details of the new directory and, finally, updates its own map of children=
as well as adding the new directory to the map of directories before commi=
tting the transaction.
The createFile
implementation is almost identical, so we do=
not show it here. Similarly, the name
and destroy
implementations are almost identical to the ones for FileI
, =
so let us move to list
:
public NodeDesc[] list(Ice.Current c) { Freeze.Connection conn= ection =3D Fre= eze.Util.createConnection(_communicator, _envName); try { Ide= ntityDirectoryEntryMap dirDB =3D new IdentityDirectoryEntryMap(connection, directoriesD= B()); for= (;;) { &nb= sp; try { &nb= sp; DirectoryEntry entry = ;=3D dirDB.get(c.id); &nb= sp; if (entry =3D=3D&nbs= p;null) { &nb= sp; throw&= nbsp;new Ice.ObjectNotExistException(); &nb= sp; } &nb= sp; NodeDesc[] result = =3D new NodeDesc[entry.nodes.size()]; &nb= sp; java.util.Iterator<NodeDesc= > p =3D entry.nodes.values().iterator(); &nb= sp; for (int i =3D&= nbsp;0; i < entry.nodes.size(); ++i) { &nb= sp; result= [i] =3D p.next(); &nb= sp; } &nb= sp; return result; &nb= sp; } catch (Freeze.DeadlockException ex)&n= bsp;{ &nb= sp; continue; &nb= sp; } catch (Freeze.DatabaseException ex)&n= bsp;{ &nb= sp; halt(ex); &nb= sp; } } } finally { con= nection.close(); } }
Again, the code is very simple: it iterates over the nodes
=
map, adding each NodeDesc
structure to the returned sequence.<=
/p>
The find
implementation is even simpler, so we do not show =
it here.