Timing Issues with pyinotify
Ok so lets look a that little issue with pyinotify, and it really is only little, but enough to keep you on your toes. So this is the issue in brief.
START LOOP 2009-05-11 21:55:39.733393 Result of notifier.check_events() = TRUE Calling notifier.process_events() <Event dir=False mask=0x20 maskname=IN_OPEN name=foo path=/tmp pathname=/tmp/foo wd=1 > END LOOP 2009-05-11 21:55:40.339782 START LOOP 2009-05-11 21:55:40.339807 Result of notifier.check_events() = TRUE Calling notifier.process_events() <Event dir=False mask=0x4 maskname=IN_ATTRIB name=foo path=/tmp pathname=/tmp/foo wd=1 > END LOOP 2009-05-11 21:55:40.340403 START LOOP 2009-05-11 21:55:40.340426 Result of notifier.check_events() = TRUE Calling notifier.process_events() <Event dir=False mask=0x8 maskname=IN_CLOSE_WRITE name=foo path=/tmp pathname=/tmp/foo wd=1 > END LOOP 2009-05-11 21:55:40.340971
So here is the culprit notifier = pyinotify.Notifier(m, e(), 0, 0, 1000) (I’ve altered the timeout to accentuate the issue). Take a careful look at the timings between the event output lines and you can see that they are uneven. This is because the timeout isn’t a spacer, its just a wait and the events are not linearly spaced.
My error was my own of course. The lesson here is read the code through twice and check it more for design flaws.
Simple Custom Event Processor for pyinotify
In my previous post on pyinotify I took a short look at the basic use of the pyinotify watcher using mostly default settings. Here I take a brief look at building a custom event processor. The event processor is called to retrieve the event data and, well, process it. The default processor simply prints the output to ‘stdout‘; mine will too. But it is just there to show how simple it is. Note that the processor actually processes the events in line so there’s no need for messy threading.
#!/usr/bin/python
import os
import pyinotify
from datetime import datetime
m = pyinotify.WatchManager()
notifier = pyinotify.Notifier(m, pyinotify.ProcessEvent(), 0, 0, 100)
m.add_watch('/tmp', pyinotify.ALL_EVENTS, rec=True)
while True:
print "START LOOP %s " % datetime.now()
try:
if notifier.check_events():
print "Result of notifier.check_events() = TRUE"
notifier.read_events()
print "Calling notifier.process_events()"
notifier.process_events()
except KeyboardInterrupt:
notifier.stop()
break
print "END LOOP %s " % datetime.now()
This is just the same code as we saw in the earlier post except that I’ve put the ‘notifier.process_events()‘ in its proper place so that it doesn’t get called in every loop execution. It is restricted by the ‘notifier.check_events()‘. To add a little info to the whole process I’ve included some print statements to illustrate the flow of execution. With this we get results that look like:
START LOOP 2009-05-07 23:02:25.025048 Result of notifier.check_events() = TRUE Calling notifier.process_events()END LOOP 2009-05-07 23:02:25.104546
Which is what you’d expect. However you will also get results that look like.
START LOOP 2009-05-07 23:02:18.808039 Result of notifier.check_events() = TRUE Calling notifier.process_events()END LOOP 2009-05-07 23:02:18.863855 START LOOP 2009-05-07 23:02:18.863912 Result of notifier.check_events() = TRUE Calling notifier.process_events() END LOOP 2009-05-07 23:02:18.864656 START LOOP 2009-05-07 23:02:18.864710 Result of notifier.check_events() = TRUE Calling notifier.process_events() END LOOP 2009-05-07 23:02:18.865415
So you can see that we get some reasonably unpredictable results.
Now let’s do something a little different and collect the event and print it ourselves a little differently. In this case we’re still collecting all events, we just wanted to mark up the output with something extra.
#!/usr/bin/python
import os
import pyinotify
from datetime import datetime
class e(pyinotify.ProcessEvent):
def process_default(self, event):
print(repr(event))
m = pyinotify.WatchManager()
notifier = pyinotify.Notifier(m, e(), 0, 0, 100)
m.add_watch('/tmp', pyinotify.ALL_EVENTS, rec=True)
while True:
print "START LOOP %s " % datetime.now()
try:
if notifier.check_events():
print "Result of notifier.check_events() = TRUE"
notifier.read_events()
print "Calling notifier.process_events()"
notifier.process_events()
except KeyboardInterrupt:
notifier.stop()
break
print "END LOOP %s " % datetime.now()
Easy huh? But there’s still a little more, there’s a problem! More on that later.
Python and pyinotify
A friend of mine posted a really good article over at blogspot about using pyinotify; after reading one of the comments there I started playing around with it. So if you want the original have a look at David Latham’s original post. I’ve simplified the code just a little to get to the essence of pyinotify. Incidentally if you’re working from Fedora like I am you might like to ‘yum install python-inotify*‘ and confirm that the module and documentation are installed. Here’s the base version.
#!/usr/bin/python
import os
import pyinotify
m = pyinotify.WatchManager()
notifier = pyinotify.Notifier(m, pyinotify.ProcessEvent())
m.add_watch('/tmp/', pyinotify.ALL_EVENTS, rec=True)
while True:
try:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
notifier.stop()
break
It works really well for this example but has one generally unwelcome side effect. It also blocks on the ‘notifier.check_events()‘ call. A blocking call is not really what you want in an event loop so it doesn’t really work too well for the core of the application. However this is overcome very simply by adding the three extra parameters defining the wait timing of the ‘pyinotify.Notifier(m, pyinotify.ProcessEvent(), 0, 0, 10)‘. In this case the block is turned to a 10 second wait courtesy of the last parameter as shown in the next code snippet below. In an application event loop you would use a value of 0 meaning that the call would not block at all.
#!/usr/bin/python
import os
import pyinotify
m = pyinotify.WatchManager()
notifier = pyinotify.Notifier(m, pyinotify.ProcessEvent(), 0, 0, 10)
m.add_watch('/tmp/', pyinotify.ALL_EVENTS, rec=True)
while True:
try:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
notifier.stop()
break
In this case the default event handler just prints XML formatted event lines to stdout. But there’s just one more unexpected side effect. You may find there are a few timing issues and how do you get the notification back to the main thread? But more on that later.
PI
Posted by admin in mathematics on April 22nd, 2009
4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + . . .)
LDAP Attributes in JNDI
Almost everything that you do with LDAP concerns the retrieval and manipulation of attributes. Due to the extensible nature of attributes finding out their types and content can be quite involved. Here is a simple sample code snip to the very basic of attribute look up including type information.
/**
* printAttributes
*
* Prints the attributes listed in ats to the stream defined by s. If ats
* is null all attributes will be listed. If ats is empty none will be
* listed.
*
* example:
* String obj = "uid=admin,ou=system";
* String[] ats = {"uid","displayName"};
* printAttributes(ctx,obj,ats,System.out);
*
*
* @param ctx initial directory context
* @param obj object in directory to retrieve attributes for
* @param ats attribute list of attribute names to retrieve, null for all
* @param s stream to write the attributes to
* @throws javax.naming.NamingException
*/
public static void printAttributes(InitialDirContext ctx, String obj,
String[] ats, PrintStream s) throws NamingException {
Attributes a = ctx.getAttributes(obj, ats);
NamingEnumeration e = a.getAll();
while (e.hasMore()) {
Attribute t = (Attribute)e.next();
s.println("<attribute name=\"" + t.getID() + "\">");
s.println(" <syntax>" + t.getAttributeSyntaxDefinition().getAttributes("") + "</syntax>");
s.println(" <schema>" + t.getAttributeDefinition().getAttributes("") + "</schema>");
NamingEnumeration f = t.getAll();
while (f.hasMore()){
Object z = f.next();
s.println(" <value>" + z + "</value>");
}
s.println("</attribute>");
}
}
This produces output that looks something like this:
<attribute name="cn">
<syntax>{x-schema=X-SCHEMA: system, x-is-human-readable=X-IS-HUMAN-READABLE: true, numericoid=NUMERICOID: 1.3.6.1.4.1.1466.115.121.1.15}</syntax>
<schema>{name=NAME: cn, commonName, substr=SUBSTR: caseIgnoreSubstringsMatch, x-schema=X-SCHEMA: system, syntax=SYNTAX: 1.3.6.1.4.1.1466.115.121.1.15, numericoid=NUMERICOID: 2.5.4.3, sup=SUP: name, desc=DESC: RFC2256: common name(s) for which the entity is known by, usage=USAGE: userApplications, equality=EQUALITY: caseIgnoreMatch}</schema>
<value>developer</value>
</attribute>
Simple Berkeley Sockets C Server Example
About Apache and Bloat
Every now and then I look at Apache and wonder about its size and complexity. Its a huge beastie now but absolutely brilliant too. I have quite a respect for it as do many around the world. But then I don’t really use very much of its functionality.
Sure its been re-factored into a modular form so that you only need to use the bits that you want but it still presents a mental preponderance thatmany would consider bloat. But bloat is a term used to describe software that has vast wads of unnessecary code that could be accomplished more simply. However Idon’t think that Apache has that. All of its code seems very purposeful, and as I indicated earlier it is reasonably easy to include or exclude as you will.
Introducing the Example
So this example is not here to show you the right way of doing things, but rather how simple the solution to you particular problem could be. In this case the example server simply serves up the time on the server machine to port 6543 for any client and then closes the stream. It can do this for many concurrent connections at once given the only complexity that is added to the server is client handling by forked process.
Now to the Code
/* -----------------------------------------------------------------------------
simple example of a server in C using very simple Berkeley sockets
This is just an example for looking at. Not really how you would actually
implement a server. For a start it does not take care of signals and isn't
capable of being implemented as a service.
----------------------------------------------------------------------------- */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <strings.h>
#include <arpa/inet.h>
/* we're not going to use arguments for this example */
#define SERV_UDP_PORT 6543
#define SERV_TCP_PORT 6543
#define SERV_HOST_ADDR "192.168.2.200"
int main (int argc, char ** argv, char ** env) {
int socket_server, socket_client, child_pid;
struct sockaddr_in address_client, address_server;
socklen_t client_address_size;
FILE *stream_client;
time_t now;
struct tm *tm;
/* open a tcp socket to listen on */
if ( (socket_server = socket(AF_INET, SOCK_STREAM, 0) ) < 0)
err_dump("server: can't open stream socket");
/* bind to our local address ans start listening so that clients can find us */
bzero((void *) &address_server, (size_t)sizeof(address_server));
address_server.sin_family = AF_INET;
address_server.sin_addr.s_addr = htonl(INADDR_ANY);
address_server.sin_port = htons(SERV_TCP_PORT);
if (bind(
socket_server,
(struct sockaddr *) &address_server,
sizeof(address_server))
< 0)
err_dump("server: can't bind local address");
listen(socket_server, 5);
for ( ; ; ) {
/* wait for client connection and accpt it when it comes */
client_address_size = sizeof(address_client);
socket_client = accept(
socket_server,
(struct sockaddr *) &address_client,
&client_address_size);
if (socket_client < 0)
err_dump("server: accept error");
if ( (child_pid = fork() ) < 0)
err_dump("server: fork error");
/* specific client process handling starts here */
else if (child_pid == 0) {
/* client process has no need of the server socket */
close(socket_server);
/* generate a file stream from the client socket */
if ((stream_client = fdopen(socket_client, "w")) == NULL) {
perror("daytimed fdopen");
return 5;
}
/* write to the client stream as per normal then close it */
if ((now = time(NULL)) < 0) {
perror("daytimed time");
return 6;
}
tm = gmtime(&now);
fprintf(stream_client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
fclose(stream_client);
exit(EXIT_SUCCESS);
/* client process has left the building */
}
/* the server has no need to keep the client socket open */
close(socket_client);
}
return 0;
}
Batch Posting to a Website
Ok so this is mostly about me playing with the Google syntax highlighter. Its basically some code I rattled up to post data from a | seperated list of post_variable|post_value pairs. Read the code to find out more. Its not a real solution to anything if you want to run tests or batch stuff use Junit, Maven or Ant. But you can have fun with my limited toy if you like – but remember as always the code is GPL and will probably kill you so watch out!
/*
* Batch processor that takes | delimitied lines of fields in a file and posts
* them to an HTTP server printing out the message and response to std out.
*/
/*
* TODO:
* Could be extended to use a configuration file for inputs, logging and could
* bennefit from SSL and proxy configuration options.
*/
package batchpost;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author mc
*/
public class batchpost {
/**
* General output device
*/
static private Logger log = Logger.getLogger(batchpost.class.getName());
/**
* main
*
* Main entry point.
*
* @param args the command line arguments
* args[0] - is the fully qualified name of the batch file to read
* args[1] - is the URL to post the transactions to
*/
public static void main(String[] args) {
/*
* TODO:
* I should validate arguments and do other things but I am skipping doing
* them because of time.
*/
processFile(args[0], args[1]);
}
/**
* processFile
*
* Takes a file apart one line at a time and hands processing off to a
* message processing method. Logs errors if the file is not found or there
* is an IO error while reading the file. Assumes that each line contains a
* complete message.
*
* @param url - path to the web server to send the messages to
* @param file - file to retrieve the message lines from
*/
static private void processFile(String url, String file) {
try {
String line;
BufferedReader br =
new BufferedReader(
new InputStreamReader(
new DataInputStream(
new FileInputStream(file))));
try {
while ((line = br.readLine()) != null) {
processMessage(url, line);
}
} catch (UnsupportedEncodingException ex) {
log.log(Level.SEVERE, "processFile - processing halted because it is " +
"not possible to encode the post string in UTF-8.", ex);
} catch (IOException ex) {
log.log(Level.SEVERE, "processFile - IO exception has halted " +
"processing of the file \"" + file + "\".", ex);
}
try {
br.close();
} catch (IOException ex) {
log.log(Level.SEVERE, "processFile - IO exception has interrupted " +
"closing of the file \"" + file + "\".", ex);
}
} catch (FileNotFoundException ex) {
log.log(Level.SEVERE, "processFile - unable to locate the file \"" +
file + "\".", ex);
}
}
/**
* processMessage
*
* Takes a line of | delimited message fields and posts them to a web server.
* Each line is composed of a list of field_name|field_value pairs such that
* the line looks like:
*
* name_1|value_1|name_2|value_2|...
*
* or
*
* name|bob charles|age|62|occupation|pro golfer
*
* The total number of | delimited fields must be a multiple of 2. The
* message fields must not contain a |.
*
* @param url - address of the web server to post this message to
* @param line - string containing a | delimited set of fields
* @throws java.io.UnsupportedEncodingException when the platform does not
* support utf-8
*/
static private void processMessage(String url, String line)
throws UnsupportedEncodingException {
String[] fields = line.split("\\|");
if (fields.length % 2 == 1) {
log.log(Level.SEVERE, "processMessage - unable to process the following" +
" message as it did not contain a multiple of 2 fields: message = " +
"\"" + line + "\"");
return;
}
String postData = createPostData(fields);
log.log(Level.INFO, "START OF DATA TO BE POSTED");
log.log(Level.INFO, postData);
log.log(Level.INFO, "END OF DATA TO BE POSTED");
String responseData;
responseData = doHTTPPost(url, postData);
log.log(Level.INFO, "START OF DATA RECEIVED FROM POST");
log.log(Level.INFO, responseData);
log.log(Level.INFO, "END OF DATA RECEIVED FROM POST");
}
/**
* createPostData
*
* Takes an array of field names and their values such that each two
* consecutive strings are a field name then value pair and prepares a POST
* data string for sending to a web server. Throws the encoding error as it
* is pointless to continue processing as this error is going to reccur for
* all post attempts if it takes place for one.
*
* @param fields - a string array of field name and value pairs.
* @return string containing the post data.
* @throws java.io.UnsupportedEncodingException
*/
static private String createPostData(String[] fields)
throws UnsupportedEncodingException {
StringBuffer buf = new StringBuffer();
String charEnc = "UTF-8";
try {
buf.append(URLEncoder.encode(fields[0], charEnc));
buf.append('=');
buf.append(URLEncoder.encode(fields[1], charEnc));
for (int i = 2; i < fields.length; i = i + 2) {
buf.append('&');
buf.append(URLEncoder.encode(fields[i], charEnc));
buf.append('=');
buf.append(URLEncoder.encode(fields[i + 1], charEnc));
}
return buf.toString();
} catch (UnsupportedEncodingException ex) {
log.log(Level.SEVERE, "createPostData - unable to encode the post data " +
"as " + charEnc + " is not available on this platform.", ex);
throw ex;
}
}
/**
* readAll
*
* Reads all of the input from the given stream and returns it in a byte array
* output stream, the stream contents can then be retrieved as a byte array or
* a string using either toByteArray or toString respectively.
*
* @param is - an open input stream to read the data from
*
* @return a byte array output stream that contains the data read from the is
* @throws IOException if there is an error reading from the input stream
*/
static private ByteArrayOutputStream readAll(InputStream is)
throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
while (!(len < 0)) {
result.write(buf, 0, len);
len = is.read(buf);
}
return result;
}
/**
* doHTTPPost
*
* performs the https post operation of the given data to the given url
* returning the result in a byte array output stream, throws an exception if
* an error takes place while performing io operations, does not deal with
* proxy servers
*
* @param data - a post formatted data stream for sending to the url server
* @param url - the target to send the post to
*
* @return a data stream of the response from the server at the url
* @throws IOException on failure of any io operation
*/
static private String doHTTPPost(String url, String data) {
String result = "";
HttpURLConnection conn = null;
try {
// set up the url connection
conn = (HttpURLConnection) (new URL(url)).openConnection();
try {
// write out the data to the server
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.getOutputStream().write(data.getBytes());
System.out.println(conn.getResponseMessage());
// disconnect and recycle the connection, return the response data
result = readAll(conn.getInputStream()).toString();
} catch (ProtocolException ex) {
log.log(Level.SEVERE, "doHTTPPost - unable to marshal the required " +
"protocol to establish a connection to \"" + url + "\"", ex);
} catch (IOException ex) {
log.log(Level.SEVERE, "doHTTPPost - IO error while attempting to " +
"perform the data post operation.", ex);
} finally {
conn.disconnect();
}
} catch (MalformedURLException ex) {
log.log(Level.SEVERE, "doHTTPPost - the URL currently being attempted " +
"to connect with is malformed \"" + url + "\"", ex);
} catch (IOException ex) {
log.log(Level.SEVERE, "doHTTPPost - IO error while attempting to set up" +
" the data post operation.", ex);
} finally {
conn = null;
}
return result;
}
}
Blinded by the Wobbly Wobbly Web
Its really hard to make a great ascetically pleasing web site, which is probably why this one is so dull, mostly because of your monitor. You might think its rude of me to blame your monitor, perhaps it is. Or is it? Actually it looks really good on my 1080p wide screen monitor with its thousands of shades of black. It even looks really good on my wife’s cool little Mac Book. But it really sucks on the screens at work.
What I’m getting at here is not really how the monitor makes it look but the challenge that we as designers face in conveying our creations to the outside world through this uncooperative medium of the web. Today we constantly fight with:
- Browser reproduction – is the box model in IE really broken, if so which one in which version of IE? Is Safari the benchmark or Opera? What about Firefox and Chrome, surely they could get it right?
- Display characteristics – how do you know what black really looks like on my screen, or someone else’s? Will your carefully shaded images show the lovely gradients of a baby’s skin or will it be uglified by the course grades available? Are the dimensions just right? How about the resolution?
- Plugin availability – all my browsers have flash block on them to stop Internet bandwidth thieves stealing what I’ve paid for with their extravagant advertising. Are all the fonts that you need there? JavaScript anyone? Will that DHTML find everything that it needs?
Of course there are lots more things that can seemingly go astray that aren’t mentioned here, so how do we solve it? Some hail standards as the way to go. Perhaps they are right, I certainly like standards. But the problem with standards is that they need to cater for every situation; and they need to be created, grow and die gracefully. Very hard to do well. Other’s take the minimalist approach claiming that it’s easier to be consistent accross diverse transmission and presentation media by making it all to simple to stuff up. But as a friend has a habit of saying this is proved wrong by the all too true adage ‘when one makes a thing idiot proof, one will only find a deeper class of idiot’. It is so easy to disprove the idea that a central control system or monopoly can render things perfect through ultimate control with one word: Microsoft.
Ultimately there’s one thing that we can’t protect ourselves from, idiots who think that this problem can be universally solved. The problem is not that the situation need be solved but that we need to recognise that it doesn’t need to be solved. You really only want to talk to those of a similar mind or a specific group so just design for them. People who want to talk to the world fail because the world quite frankly isn’t interested and isn’t listening. So don’t solve that problem.
Solve the problem of communicating with your target market. Our only concern should be with our target audience and communication with them. Smaller number of people means a smaller number of problems and perhaps we can manage that. So take this web site, its target market is me. I blog mostly to see my thoughts on the screen because it helps me think about them. Some times it helps me understand the rubbish that I think. Other times it helps me develop some really neat ideas and concepts. If other people read it that’s fine, I might even refer people to it to understand something that I don’t have time to explain but that I’ve written about. But at the end of the day its just a place for my thoughts and ramblings. Do the same with your market, are they old people with simple set ups they bought from Wall Mart? Are they techno-junkies how are likely to have the latest in colour reproducing screens? Will they actually consume your content how you want them too?
At this point I need to wrap up with a tie to the opening about aesthetics and the variances that make it hard with one last and the most significant point, the people factor. Every now and then someone says to me, ‘it shouldn’t be black with white ’cause its hard to read’ or ‘if you don’t make it colourful it won’t be a site that people like to spend time on’ or my favourite ‘I’m an artist / designer / architect and I know what looks good and that doesn’t’. But even the so called professionals are really just espousing their own opinions and preferences unless they can come up with concrete reasons that demonstrates that your target market will be affected by what they have observed. So take the hints from above and comments of others into consideration when designing your site, but most importantly survey your target market to make sure your web site works for your audience.
All good points, however there is a point that needs to be understood; I’m sad if you don’t like my web site but that’s OK because it works for my audience, me. Hopefully everybody else has just as much success designing for their audience too.
Best of luck to all of you, M.C.
Do you have a Spring in your Wheel?
Spring is one of the frameworks of the moment but is it really a great framework or something entirely different? Most of the frameworks that I know of are fairly targeted and form a particular function. All of the good ones that I know of have a particular place in life; a frame work for doing some thing. Spring does not. But there’s gold in that there code…
Probably the most disappointing thing about Spring is its documentation. The entry barrier for integrating Spring with the rest of your tool set is quite high by its limited documentation. Yes there is a great volume of the stuff, but it appears to be limited to those who are already in ‘the know’. In other words if you really want to get a handle on Spring from scratch you have two choices: either buy a book or surf the web and hope.
But this is because we’ve been mislead. Struts is a framework, Spring is not. So a conclusive introduction to Struts is easy to write and easy to understand because it frames an idea. However as Spring does not frame a central idea and so writing an introduction to Spring is almost a lost cause from a framework point of view.
Spring is an SDK. A jolly brilliant SDK. When you look at the Spring development kit you find that it will work with what you have, doesn’t force a great monster of a framework on you and it riddled with simply brilliant concepts and patterns. As you start to delve into it you realise that the guys who put it together have brought together a great collection of conceptual gems. If you keep watching the SDK grow you will see more and more of them being added to the SDK as components for use in groups or by themselves.
Underlying all of these gems is a strong theme of patterns and approaches to design that pervades all corners of the SDK making it feel and look like a slick whole. These concepts include such things as the pervasive use of IOC accross the board to the modular adoption of specific patterns like MVC, which IS a framework, that are included in such a way as to let the designer include or exclude them in accordance with usefulness. In most ‘frameworks’ that I’ve seen its either all features or a difficult configuration to remove unwanted bloat.
So is Spring a case against bloat ware? No. Bloat ware is a term given to things that produce copious quantities of bulk which could easily be replaced by more stream lined solutions. When you see a product that makes you think ’surely that could be done with less’ you’re probably looking at bloat ware. Spring is not there for that purpose. You see almost everything it does belongs to the school of computing that is termed ‘middle ware’. Middle ware is almost by definition bloat.
However tempting to label Spring as bloat ware it does perform two very critical tasks often overlooked by those bloat ware vigilantes out there; it makes your software manageable. Everything that Spring does could be pared back to make it faster more efficient etc but you could barely dent it at the cost of making your software less easy to design, implement, install and operate. Carefully add Spring to your project and you are adding real value in terms of future proofing your product and saving yourself a ton of grief.
So if this is a SDK how does it compare to other SDK’s? To answer this question you need to think about perspective. If you look at the JDK, the standard C/C++ libraries, X windows xt or even Microsoft’s extensive .Net SDK you will find products that are built bottom up. That is products that have been built from a wide array of small useful components into a general smorgasbord of fancy components and knick knacks. Spring is built the other way around, it has been built from concepts to components. So when you look at Spring you see a large number of cohesive modules rather than the gravel and rocks of most other SDK’s. Effectively Spring takes the concepts and makes them real relying on other foundational tool kits to get the simple things in life done. Top down.
So Spring is not a framework, it is not a way of protecting you from bloat ware, it is an implementation of concepts, patterns and design built on the basics for you to cherry pick as you please and improve your product and its operation. Its an SDK like so many others; hardly a reinvention of an old wheel, but a new sporty set of spokes to use in building your own.
Simple PHP MVC
One of the much talked about patterns of the moment is the model-view-controller or model-view-presenter or any of the undoubtedly thousands of variants thereof. Here’s a deliberately really simple one to illustrate the pattern. If you can read it and understand it I hope its useful to you.
Interestingly a vague attempt at inversion of control, another design pattern which IMHO is more of a guide line, is in play here. It is done specifically to allow good component separation and useful test patterns. Hence as long as you have a class that fits the dependency obligations you can use it with the ones given here.
Now for the seriously crazy bit, the code on this page has not been tested to work. Its just here to give you some idea of what the model-view-controller pattern and how it could be implemented.
mvc.controller.php
Overview:
At the heart of each variant implementation of the model view controller pattern is the controller. A controller basically directs requests to the appropriate model or view processing functionality. In the case below it only makes the distinction between the two types and not the instances of functionality.
Some controllers combine a lot more but its not actually necessary and generally ends up making the controller very configuration and platform specific. By keeping configuration out of the controller we make it as general as it can be.
Notes:
Note that there is no exception handling here either. It is not wanted. Consider for a moment that if an exception was not caught, there would be no response to the client, which is a security requirement in many instances. However it there would be a log to trace to the point of failure because of the log entry call.
If exception handling were introduced it would compromise the generic nature of the controller as the controller would need to know what type of response to send to the client. For example if the error was thrown before the content of the request could be analysed and what data would you respond to the request with? HTML? XML? A media stream? A GIF? A JPG? The rule of thumb here is only respond if you know what language to talk in, if you don’t know the requested format you can’t even use clever configuration to determine how respond to a request.
Documented dependencies:
- class ‘Model’ : method ‘process_request’ : parameters ‘request to process’ : returns ‘new request for either model processing or view generation’
- class ‘View’ : method ‘process_request’ : parameters ‘request to process’ : returns ‘a view generated from the accumulated data in the request’
Undocumented dependencies:
- class ‘Log’ : method ‘writestr’ : parameters ’string to log’, ‘name of log level to record string at’ : returns ‘nothing’
- class ‘Log’ : method ‘writereq’ : parameters ‘request to log’, ‘name of log level to record string at’ : returns ‘nothing’
- class ‘Request’ : method ‘is_a_model_request’ : parameters ‘none’ : returns ‘true if the request is to be processed by the model’
Listing:
<?php
Log::getInstance()->writestr( "file:".__FILE__.":loaded", "debug" );
class Controller {
private $model;
private $view;
/*
* build this controller object
*/
public function __Construct ( $model, $view ) {
$this->model = $model;
$this->view = $view;
}
/*
* process a request
*/
public function process_request ( $request ) {
Log::getInstance()->writestr(
"class:".__CLASS__.":process_request", "debug" );
Log::getInstance()->writereq( $request, "debug" );
/*
* first process the model request
*/
$new_request = $this->model->process_request( $request );
/*
* if this request results in another model request
*/
if ( $new_request->is_a_model_request( ) )
return $this->process_request( $new_request );
/*
* otherwise generate view
*/
return $this->view->process_request( $new_request );
}
}
?>
mvc.model.php
Overview:
A model is responsible for the production of data for the view. Essentially it contains access points to the various methods of the underlying application called actions. In this case it simply has a list of actions that it locates and then executes the action sending the results back to the caller.
Notes:
As with the controller (see above) the model does little more than the basics leaving other classes that know what they are doing to handle the specifics of content. In line with this the model simply logs progress and fails silently.
Undocumented dependencies:
- class ‘Log’ : method ‘writestr’ : parameters ’string to log’, ‘name of log level to record string at’ : returns ‘nothing’
- class ‘Log’ : method ‘writereq’ : parameters ‘request to log’, ‘name of log level to record string at’ : returns ‘nothing’
- class ‘Actions’ : method ‘find_requested_action’ : parameters ‘request to locate action for’ : returns ‘an Action object’
- class ‘Action’ : method ‘process_request’ : parameters ‘request to process’ : returns ‘new request for either model processing or view generation’
Listing:
<?php
Log::getInstance()->writestr( "file:".__FILE__.":loaded", "debug" );
class Model {
private $actions;
/*
* build this model object
*/
public function __Construct ( $actions ) {
$this->actions = $actions;
}
/*
* process a request
*/
public function process_request ( $request ) {
Log::getInstance()->writestr(
"class:".__CLASS__.":process_request", "debug" );
Log::getInstance()->writereq( $request, "debug" );
/*
* find the action from the list of actions
*/
$action = $this->actions->find_requested_action( $request );
/*
* run the action and return the resulting request
*/
return $action->process_request( $request );
}
}
?>
mvc.view.php
Overview:
A view is called to format the data retrieved from the model into the format that the original client requested. Essentially it contains access points to the various view methods of the underlying application. In this case it simply has a list of views that it locates and then executes sending the results back to the caller.
Notes:
Now those sharp of eye will realise something; this looks pretty much like the model class above, and you’d be right. Apart from a a simple renaming of a few items the code is the same. In practice both the above model class and this view class would be implemented as a single class and simply configured differently. They are only provided in their separate formats here to highlight the model-view-controller form.
Undocumented dependencies:
- class ‘Log’ : method ‘writestr’ : parameters ’string to log’, ‘name of log level to record string at’ : returns ‘nothing’
- class ‘Log’ : method ‘writereq’ : parameters ‘request to log’, ‘name of log level to record string at’ : returns ‘nothing’
- class ‘Views’ : method ‘find_requested_view’ : parameters ‘request to locate view for’ : returns ‘an View object’
- class ‘View’ : method ‘process_request’ : parameters ‘request to process’ : returns ‘the final generated view’
Listing:
<?php
Log::getInstance()->writestr( "file:".__FILE__.":loaded", "debug" );
class View {
private $views;
/*
* build this view object
*/
public function __Construct ( $views ) {
$this->views = $views;
}
/*
* process a request
*/
public function process_request ( $request ) {
Log::getInstance()->writestr(
"class:".__CLASS__.":process_request", "debug" );
Log::getInstance()->writereq( $request, "debug" );
/*
* find the view from the list of views
*/
$view = $this->views->find_requested_view( $request );
/*
* process the request and return the resulting view
*/
return $view->process_request( $request );
}
}
?>