Managing the configuration of an application is a consistent pain-point for developers, administrators, and business analysts.
Often in production environments, configuration is isolated as files on the local disk, limiting easy access by all but administrators. Another common approach is to store configuration in a database or LDAP. While this have benefits of a local disk file, it lacks the ability to manage properties as naturally as a file.
The solution proposed uses a database and WebDAV to help resolve many negatives to do with application configuration in both a simple and lightweight way.
In the first blog in the WebDAV series, we discussed a simple example of how you can use WebDAV to create an online network drive, to illustrate how WebDAV can quickly enhance your application. I recommend reading it first before moving onto this blog.
Solution Features
We can help resolve those pains and have added benefits just by using a database and WebDAV. This approach allows for accessibility (both read & read/write) for a larger set of user types, and allows those users the ability to:
- view and edit individual properties from their favorite text editor
- backup & restore property files quickly, by just zipping them up for example
- migrate property files from one environment to another by copying over a folder
All this is packaged as a simple add-on that can be quickly integrated to your existing web application.
Design
File & Folder Structure
This is an example of how the mapped drive would appear when mounted:
Folders are nested and can be named accordingly to the type of properties that are held. Property files appear as you would expect under folders (or at the root level as well). Here we have put configuration specific to countries under the “country configs” folder with the country in the name of the file.
Schema
Below is the simple schema that is used:
It allows having a recursive tree structure by the self-referencing folder table. Files contain a set of properties and existing within a folder. Properties are stored in the property table as String key-value pairs. Basic DAO classes facilitate CRUD operations on the files and folders.
Properties to File and File to Properties Transformation
To keep things simple and consistent, we use the existing methods present in the Properties
class to generate a file from properties and vice versa. By using this method, properties appear in the file content as key-value pairs, one per line separated by ‘=’ as you commonly see String properties represented.
Usage
Of course, having your application read these properties is vital. To that effect, there is a class called WebDAVProperties where your application can both read and write properties:
Properties WebDAVProperties.getProperties(String path)
where path is the fully qualified path of the properties resource.
void WebDAVProperties.saveProperties(String path, Properties props)
where path is the fully qualified path of the properties resource.
Saving the properties using the above method calls the necessary DAOs to persist the properties in the schema.
Implementation
Class Diagram
Model Objects
There are 2 model objects in use in this approach (besides the java.util.Properties
object). Both are under the package com.northconcepts.webdav.model
.
The
FolderInfo
self-references to allow it to keep track of it’s parent. For each FolderInfo
, there can be any number of FileInfos
, directly mapping to a typical file systems model. Each FileInfo
has a Properties class to maintain the contents of the file.
DAO
Under the package com.northconcepts.webdav.dao you will see 3 DAO classes: FileDAO
, FolderDAO
, and PropertyDAO
.
FileDAO
List<FileInfo> getFilesInFolder(FolderInfo fi)
– gets the list of files that are in the specified folder
FolderDAO
FolderInfo getFolderById(final int id)
– gets the folder by it’s idFolderInfo getParentFolder(final FolderInfo f)
– gets the parent folder by the provided folder’s idList<FolderInfo> getSubFolders(final FolderInfo id)
– gets the subfolders by the provided folder’s idFolderInfo createFolder(final String name, final FolderInfo parentFolder)
– persists a folder based on the specified values and returns that foldervoid deleteFolder(final int id)
– deletes the folder by it’s idvoid moveFolder(final int id, final int destFolderId, final String name)
– moves the folder of the specified id to below the specified destination folder id and rename the folder to the new name
PropertyDAO
Properties getProperties(final int fileId)
– gets all the property key-value pairs for the given file’s idFileInfo createProperties(final String name, final Properties props, final FolderInfo folder)
– persists a properties based on the specified key-value pairs, name and folder location
WebDAV Implementation
For a simple Milton Java WebDAV Server Library in-memory implementation example with just a single file and no folders, please review the previous blog post. Similar to the aforementioned model objects, we have 2 classes implementing the various WebDAV Resource interfaces: DavFolder and DavFile.
WebDAV Resource Interfaces
Key to this part of the implementation is understand the behavior implications by implementing specific Resource interfaces.
GetableResource
– allows the content of the resource can be retrievedDeletableResource
– allows the resource to be deletedCollectionResource
– the resource to have children implying the directory behaviorPutableResource
– allows file resources to be created as children below itMakeCollectionableResource
– allows folder resources to be created as children below itMoveableResource
– allows the resource to be moved to a child of another folder
Properties Processing
Since we want to be able to read the contents of a file as well as delete the file, we need to implement the interfaces GetableResource
and DeletableResource
.
Below is the sendContent(...)
method of the DavFile
class, required by the GetableResource
interface:
1 2 3 4 5 |
public void sendContent(OutputStream out, Range range, Map params, String contentType) throws IOException, NotAuthorizedException, BadRequestException { PrintStream ps = new PrintStream(out); p.getProps().list(ps); ps.flush(); } |
This method is using the existing list method of the Properties
class to render the content of the file.
The opposing act of persisting properties by file creation, that is driven by the createNew(...)
method of the DavFolder
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public com.bradmcevoy.http.Resource createNew(String name, InputStream is, Long length, String contentType) throws IOException, ConflictException, NotAuthorizedException, BadRequestException { log.debug("attempting to create a new file in this folder name={} length={} contentType={}",new Object[]{name,length,contentType}); //CHECK IF INVALID NAME if (Util.isEmpty(name)) { log.error("bad request when creating a file"); throw new BadRequestException(this); } //ASSERT NO CONFLICT WITH THE NAME assertNoConflict(name); //LOAD STREAM TO A PROPERTIES OBJECT Properties p = new Properties(); p.load(is); //PERSIST THE PROPERTIES AND RETURN THE DAV-WRAPPED NCPROPERTIES return new DavFile(PropertyDAO.createProperties(name,p,f)); } |
Once again we are using a provided method in the Properties
class to parse and load the properties coming from the input stream.
MyResourceFactory Implementation
Finally, to be able to produce the file system structure as designed, the class implementing the ResourceFactory
uses recursion to determine, based on the path, whether to offer back a appropriate folder, or the appropriate file.
The recursive search, shown below, essentially works by iterating through the path, where each part of the path is determined if it’s a folder or a file; if its a folder and there are more path parts then we recursively search within that folder with the remainder of the path.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public class MyResourceFactory implements ResourceFactory { private static final Logger log = LoggerFactory.getLogger(MyResourceFactory.class); @Override public Resource getResource(String host, String strPath) { Path path = Path.path(strPath); log.debug("getting resource - host={} path={}",host,path); //STRIP PRECEEDING PATH path = path.getStripFirst().getStripFirst(); log.debug("path trimmed - new webdav parts={}",path.getParts()); if (path.isRoot()) { log.debug("path is just the root, returning the Root Folder"); return new DavFolder(FolderDAO.getRootFolder()); } else { return recursePath(path, FolderDAO.getRootFolder()); } } private Resource recursePath(Path path, FolderInfo parentFolder) { log.debug("recursing path - path={} parentFolder={}",path,parentFolder); for (FolderInfo f : FolderDAO.getSubFolders(parentFolder)) { if (path.getFirst().equals(f.getName())) { if (path.getLength()==1) { return new DavFolder(f); } else { return recursePath(path.getStripFirst(), f); } } } for (FileInfo p : FileDAO.getFilesInFolder(parentFolder)) { if (path.getFirst().equals(p.getName())) { if (path.getLength()==1) { return new DavFile(p); } else { return null; } } } return null; } } |
Download & Install
Step 1
The WebDAV download contains the entire source code (including Eclipse project). The source code is licensed under the terms of the Apache License, Version 2.0. Download this source code and import the project to your Eclipse workspace.
Step 2
Run the MySQL create_script.sql
script located in the project folder src-database/scripts
in your MySQL database. If you are using another type of DB other than MySQL, please refer to the schema diagram under src-database/model
to create the three necessary tables as illustrated.
Step 3
Add a Resource
to define a data source for the application to use in your Tomcat context.xml file. Below is an example:
<Resource name="jdbc/MySQLDB" auth="Container" type="javax.sql.DataSource" username="root" password="password" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test?autoReconnect=true"/>
Step 4
Create, if it doesn’t already exist, a Apache web server instance in Eclipse and publish the application to that server. Then start the application server.
Step 5
Configure your WebDAV client to point to the following URL http://localhost/webdav-example-2/config/ to access the properties through WebDAV.
Concluding Thoughts
Certainly we have all felt the pain of managing application configuration from development environments through to production. This approach attempts to solve some of this pain. The example code provided can be plugged into your application to allow you to quickly move forward on improving the configuration efficiency and accessibility. More valuable, the design and implementation could inspire you to consider other objects your application uses or persists which can be expressed as a file system, giving you unprecedented accessibility.
I’m I suppose to populate the content in the SQL database prior to running the application, in order to see files and Folders?
Hello Kuhan,
thanks for the easy to user WebDAV server code. The only problem I have is to create folders and/or files. The Mac OS file systems somehow seems to be read only – although it is not normally -.
So I had to enter the folders and files records by hand in MySql.
My example-2 web app then can read and shows the folders and fields using my finder Mac file system app.
Can you please give me a hint what I need to do to get writer and delta permissions?
Thanks for help,
Dirk
Can milton api support Opening word document with Microsoft office 2010?