Exactly how LightWire works

Since I'm writing my own Dependency Injection engine (LightWire), I though I might as well blog about the process and the design decisions in case anyone is interested in the thinking required to create a simple DI engine. If anyone notices any design flaws, I’d love to hear about them!!! You can follow along with the code (it’ll be updated before tomorrow morning) at http://lightwire.riaforge.org using either the zip file or the subversion repository.

[UPDATE] Just added all the code to this posting as well, so you can follow along without having to download files!

The init() method is fairly simple. It just needs to set all of the properties in the variables scope and then if lazy loading is off, it needs to loop through all of the singletons and if they don’t already exist in the variables scope in LightWire, it should create them. How would they already exist? Well, because each singleton may be dependent on other singletons. Every getObject() call creates not only the requested object, but any of its dependencies, so even when initializing LightWire, it is quite likely that by the time you get to some of the later singletons, they will already exist.

<cffunction name="init" returntype="LightWire" access="public" output="true" hint="I initialize the LightWire object factory.">
   <cfargument name="LightWireConfigFilePath" required="yes" hint="I am the fully qualified cfinclude path to the configuration file to include - including the file path, file name and file extension.">
   <cfset var key = "">
   <!--- Include all of the class configuration properties --->
   <cfinclude template="#arguments.LightWireConfigFilePath#">
   <cfparam name="variables.Singleton" default="#StructNew()#">
   <cfparam name="variables.LightWire.LazyLoad" default="1">
      If (NOT variables.LightWire.LazyLoad)
         // Loop through every bean definition          For (key in variables.Bean)
             // Create every singleton              If (variables.Bean[key].Singleton)
               if(not StructKeyExists(variables.Singleton,key))
                  // Only create if hasn't already been created as dependency of earlier singleton                   variables.Singleton[key] = variables.getObject(key,"Singleton");
   <cfreturn This />

The Configuration File
I tend to prefer programmatic config files for a number of reasons (it is well worth reading both that section and the whole article). That said, I understand the benefits of XML configuration files as well. For now I’ve decided just to provide support for a programmatic configuration file and I’ll add a simple translator for reading an XML file or object when someone asks for one (I’ll base the XML off of the Java Spring DTD).

The programmatic configuration file is very simple indeed with the following system settings:

Lazy load - If this is set to 1, LightWire will just load components and their dependent components as required. If 0, LightWire will load all of the singletons in a project on first being called. Lazy loading speeds up first page load for complex applications – especially when testing. Recommended setting: 1 (lazy load on). Please note that lazy loading is only available at a system level – not a bean level.
e.g. variables.LightWire.LazyLoad = 1
Base class path - The base class path allows you to more easily reuse configurations between applications. Instead of having to change the class path for every single bean, you can just change the base class path in one place (and you can even set it using a variable if the base class pass is always #application.name#.com or something similar – one of the benfits of programmatic config files).
e.g. variables.LightWire.BaseClassPath = "myapp.com";

Then for every bean in the application, you need to create a structure under variables.Bean with the following required and (if wanted) optional properties:
Singleton - Whether the bean is a singleton or a transient. 1 = singleton.
e.g. variables.Bean.UserService.Singleton = 1;
Path - The path to the bean relative to the base class path. Can be blank if it is in the base class path directory.
e.g. variables.Bean.UserService.Path = "model.user";

Constructor dependencies - An optional comma delimited list of the names of any beans that need to be passed to the init() method of the bean.
e.g. variables.UserService.ConstructorDependencies = "UserDAO,UserGateway";
Setter dependencies - An optional comma delimited list of any bean names that need to be injected using setter injection. e.g. variables.testService.SetterDependencies = "CompanyService";
Mixin dependencies - An optional comma delimited list of any bean names that need to be injected using mixin injection.
e.g. variables.UserService.MixinDependencies = "ProductService";
Constructor Properties - An optional struct containing any non-bean constructor properties required by the init() method. Can contain any simple or complex data types within the struct which is passed into the init() method using ArgumentCollection after having any constructor dependencies (beans) added automatically.
e.g. variables.UserService.ConstructorProperties.PropertyName = "This Value";

Sample config file:

   // LIGHTWIRE PARAMETERS    // LazyLoad - Should LightWire use lazy loading or just load all singletons on startup?    // SYNTAX: variables.LightWire.LazyLoad = [1/0];
   variables.LightWire.LazyLoad = 0;
   // Base Class Path - The base path including mapping to your components directory    // SYNTAX: variables.LightWire.BaseClassPath = "myproject.com";    variables.LightWire.BaseClassPath = "lw2.com";
   // LIGHTWIRE BEAN DEFINITIONS    // CLASS PATH (Required: needed for every Singleton and Transient class - may be of zero length)    // Syntax: variables.Bean.[ClassName].Path = "path"    // Example: variables.Bean.PageService.Path = "custom.model.Page";
   // CONSTRUCTOR DEPENDENCIES (only needed if the object has one or more constructor dependencies)    // Syntax: variables.Bean.[ClassName].ConstructorDependencies = "[comma delimited list of class names it depends on]";    // Example: variables.Bean.PageService.ConstructorDependencies = "PageDAO,UserService";
   // SETTER DEPENDENCIES (only needed if the object has one or more setter dependencies)    // Syntax: variables.Bean.[.ClassName]SetterDependencies = "[comma delimited list of class names it depends on]";    // Example: variables.Bean.PageService.SetterDependencies = "PageDAO,UserService";
   // MIXIN DEPENDENCIES (only needed if the object has one or more mixin dependencies)    // Syntax: variables.Bean.[ClassName].MixinDependencies = "[comma delimited list of class names it depends on]";    // Example: variables.Bean.PageService.MixinDependencies = "PageDAO,UserService";    
   // CONSTRUCTOR CONFIGURATION PROPERTIES (If you'd like to be able to set them here as opposed to in the class init files)    // Syntax: variables.Bean.[ClassName].ConstructorProperties = "[struct containing all properties required except for beans]";    // Example: variables.Bean.PageService.ConstructorProperties.MyProperty = "MyValue"; </cfscript>

Creating Objects
Whether you are lazy loading or not, getObject() is the private method that does all of the heavy lifting. It is called by the init() method (if you are not lazy loading) and by LightWire’s two other public methods: getSingleton() and getTransient(). Each takes the name of an object and calls getObject(ObjectName,”Singleton”) or getObject(ObjectName,”Transient”) respectively. Both return a fully loaded object with all of its dependencies resolved and injected (constructor, setter and mixin).

<cffunction name="getSingleton" returntype="any" access="public" output="false" hint="I return a LightWire scoped Singleton with all of its dependencies loaded.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to generate.">
      // If the object doesn't exist, lazy load it       if(not StructKeyExists(variables.Singleton, arguments.ObjectName))
         variables.Singleton[arguments.ObjectName] = variables.getObject(arguments.ObjectName,"Singleton");
   <cfreturn variables.Singleton[arguments.ObjectName] />   

<cffunction name="getTransient" returntype="any" access="public" output="false" hint="I return a transient object.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to create." />   
   <cfreturn variables.getObject(arguments.ObjectName,"Transient") />

<cffunction name="getObject" returntype="any" access="private" output="false" hint="I return a LightWire scoped object (Singleton or Transient) with all of its dependencies loaded.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to return.">
   <cfargument name="ObjectType" type="string" required="yes" hint="I am the type of object to return (Singleton or Transient).">
      // Firstly get a list of all constructor dependent singleton objects that haven't been created (if any) - n levels deep       var ObjectstoCreateList = variables.getDependentObjectList(arguments.ObjectName);
      var LoopObjectName = "";
      var TemporaryObjects = StructNew();
      var Count = 0;      
      var ListLength = 0;      
      var ReturnObject = "";      
      var LoopObjectList = ObjectstoCreateList;      
      // Then create all of the dependent objects       while (ListLen(LoopObjectList))
         // Get the last object name          LoopObjectName = ListLast(LoopObjectList);
         // Call createNewObject() to create and constructor initialize it          variables.Singleton[LoopObjectName] = variables.createNewObject(LoopObjectName,"Singleton");
         // Remove that object name from the list          ListLength = ListLen(LoopObjectList);
         LoopObjectList = ListDeleteAt(LoopObjectList,ListLength);
      // Then create the original object       ReturnObject = variables.createNewObject(arguments.ObjectName,arguments.ObjectType);
      // And if it is a singleton, cache it within LightWire       If (arguments.ObjectType EQ "Singleton")
         variables.Singleton[arguments.ObjectName] = ReturnObject;
      // Then for each dependent object, do any setter and mixin injections required       LoopObjectList = ObjectstoCreateList;
      while (ListLen(LoopObjectList))
         // Get the last object name          LoopObjectName = ListLast(LoopObjectList);
         // Call setterandmixinInject() to inject any setter or mixin dependencies          variables.Singleton[LoopObjectName] = variables.setterandMixinInject(LoopObjectName,variables.Singleton[LoopObjectName]);
         // Remove that object name from the list          ListLength = ListLen(LoopObjectList);
         LoopObjectList = ListDeleteAt(LoopObjectList,ListLength);
      // Finally for the requested object, do any setter and mixin injections required       ReturnObject = variables.setterandMixinInject(arguments.ObjectName,ReturnObject);

   <cfreturn ReturnObject>

The first thing getObject() does is to get a list of all of the dependent constructor objects n-levels down by calling getDependentObjectList(). getDependentObjectList() just takes the name of an object (singleton or transient) and looks to see if it has any constructor dependencies, recursively. So, if you passed in “UserService“, it might return UserDAO, DataSource which means that UserService requires UserDAO and that in turn required Datasource.

<cffunction name="getDependentObjectList" returntype="string" access="private" output="false" hint="I return a comma delimited list of all of the dependencies that have not been created yet for an object - n-levels down.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to get the dependencies for.">
      var ObjectDependencyList = "";
      var TempObjectDependencyList = "";
      var ObjectstoCreateList = "";
      var LoopObjectName = "";
      var LoopObjectDependencySet = "";
      var CircularDependency = "";
      var ListLength = "";
      var NewObjectName = "";
      var Position = "";
      var KeyType = "ConstructorDependencies";
      If (structKeyExists(variables.Bean[arguments.ObjectName], KeyType))
         {ObjectDependencyList = variables.Bean[arguments.ObjectName][KeyType];}

      // Add the original object name to each element in the object dependency list for circular dependency checking       For (Count = 1; Count lte listlen(ObjectDependencyList); Count = Count + 1)
         // Get current object name          LoopObjectName = ListGetAt(ObjectDependencyList, Count);
         // Prepend it with ObjectName          LoopObjectName = ListAppend(arguments.ObjectName,LoopObjectName,"|");
         // Add it to the new object dependency list          TempObjectDependencyList = ListAppend(TempObjectDependencyList,LoopObjectName);
      // Replace the original object dependency list with the one prepended with its dependency parent for circular dependency resolution checking       ObjectDependencyList = TempObjectDependencyList;
      while (ListLen(ObjectDependencyList))
         // Get the first object dependency set on the list          LoopObjectDependencySet = ListFirst(ObjectDependencyList);
         // Get the list of the object name within that dependency set          LoopObjectName = ListLast(LoopObjectDependencySet,"|");
         // Remove that last record from the list          ListLength = ListLen(LoopObjectDependencySet,"|");
         LoopObjectDependencySet = ListDeleteAt(LoopObjectDependencySet,ListLength,"|");
         If (not StructKeyExists(variables.Singleton,LoopObjectName))
            // This object doesn't exist             // Firstly make sure the dependency != circular             If (ListFindNoCase(LoopObjectName,LoopObjectDependencySet,"|"))
               CircularDependency = ListAppend(CircularDependency,"#LoopObjectName# is dependent on a parent. Its dependency path is #LoopObjectDependencySet#");
               // If it already exists on the list of objects to create remove it from where it is                while (ListFindNoCase(ObjectstoCreateList,LoopObjectName))
                  Position = ListFindNoCase(ObjectstoCreateList,LoopObjectName);
                  ObjectstoCreateList = ListDeleteAt(ObjectstoCreateList,Position);
               // Add it to the list of dependent objects to create                ObjectstoCreateList = ListAppend(ObjectstoCreateList,LoopObjectName);         

               // And we need to add its dependencies to this list if it has any                If (StructKeyExists(variables.Bean[LoopObjectName], KeyType))
                  // Set the parent dependency set for this object                   LoopObjectDependencySet = ListAppend(LoopObjectDependencySet,LoopObjectName,"|");
                  For (Count = 1; Count lte listlen(variables.Bean[LoopObjectName][KeyType]); Count = Count + 1)
                     // Get current object name                      NewObjectName = ListGetAt(variables.Bean[LoopObjectName][KeyType], Count);
                     // Firstly make sure the new dependency != circular                      If (ListFindNoCase(LoopObjectDependencySet,NewObjectName,"|"))
                        CircularDependency = ListAppend(CircularDependency,"#NewObjectName# is dependent on a parent. Its dependency path is #LoopObjectDependencySet#");
                        // Append new object with parent dependency to object dependency list                         ObjectDependencyList = ListAppend(ObjectDependencyList,LoopObjectDependencySet & "|" & NewObjectName);         
         // Remove the current object name from the list          ObjectDependencyList = ListDeleteAt(ObjectDependencyList,1);
   <cfreturn ObjectstoCreateList>

Basically, getDependentObjectList() just gets a list of all of the dependencies for the requested object that don’t already exist (if they’re already created, they don’t need to be re-created). It then gets a list of all of the dependencies for any dependent objects recursively until there are no dependencies left.

Please note that sometimes a dependency might come up more than once. For instance, if UserService depends on UserDAO and UserGateway and both depend on DataSource, then assuming none of them have yet been created, getDependentObjectList() would naively return UserDAO, DataSource, UserGateway, DataSource. I have added a while loop to remove any existing references, so the function will actually return UserDAO, UserGateway, DataSource. It is important that it is only the last instance that is left as we will be constructing from last to first, so if both DAO and Gateway need DataSource, it must be constructed before either of them.

Circular Dependencies
getDependentObjectList() needs to test for circular dependencies. In the case of constructor dependencies, it needs to fail if there are any circular dependencies and in the case of setter dependencies, it just needs to be smart enough not to get into an infinite loop.

The only way to find circular dependencies is to keep track of the dependency path for each object and to make sure that you don’t add a dependent object that is on its own parent path. So, if ProductService is dependent on ProductDAO which is dependent on DataSource, it is NOT OK for DataSource to depend on ProductService or ProductDAO. I’m doing this by adding pipe delimited list of dependency sets to the ObjectDependencyList. I don’t love this approach, but it keeps the code fairly simple and will catch any circular dependencies and seems to be working well and catching all of the test cases I can think of.

Creating Objects
The next step is to create all of the objects required, starting at the right hand most end of the list and working back. Each object needs to be created and constructed (by calling init()) before the previous object can be called. All of the creation (including pulling together any constructor properties and objects) is handled by createNewObject() which is called once for each dependent object and then finally once for the object originally requested.

<cffunction name="createNewObject" returntype="any" access="private" output="false" hint="I create a object.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to create.">
   <cfargument name="ObjectType" type="string" required="yes" hint="I am the type of object to create. Singleton or Transient.">
      var ReturnObject = "";
      var InitStruct = StructNew();
      var TempObjectName = "";
      var Count = 0;
      var KeyType = "ConstructorDependencies";
      // Get the configured object path       var ObjectPath = "#variables.LightWire.BaseClassPath#.#variables.Bean[arguments.ObjectName].Path#.#arguments.ObjectName#";
      // if the objectPath is empty correct the dot path       ObjectPath = Replace(ObjectPath,"..",".","all");
      // Get the initialization data (if any)       If (StructKeyExists(variables.Bean[arguments.ObjectName], "ConstructorProperties"))
         {InitStruct = variables.Bean[arguments.ObjectName].ConstructorProperties;}
      // Add any constructor dependencies   
      If (StructkeyExists(variables.Bean[arguments.ObjectName], KeyType))
      For (Count = 1; Count lte listlen(variables.Bean[arguments.ObjectName][KeyType]); Count = Count + 1)
         TempObjectName = ListGetAt(variables.Bean[arguments.ObjectName][KeyType], Count);
         InitStruct[TempObjectName] = variables.singleton[TempObjectName];

      // Create the object and initialize it       ReturnObject = CreateObject("component",ObjectPath).init(ArgumentCollection=InitStruct);

      // Give it the AddObject method to allow for mixin injection       ReturnObject.MixinObject = variables.MixinObject;
   <cfreturn ReturnObject>

Setter and Mixin Injection
Before the objects can be returned, they need to have any non-constructor injections taken care of. As well as the standard setter injection (based on set%ObjectName%() methods), I have also added a mixin injection option. The difference between setter and mixin injections is that setter injection requires a set%objectName%() method whereas mixin injection doesn’t. If you need to do anything more than just pass the dependent object in, you want to use setter injection so the setter method can handle any custom logic. If not, you can just use mixin injection to avoid having to mess up your objects API with setter methods that are really only for pseudo-construction purposes.

<cffunction name="setterandMixinInject" returntype="any" access="private" output="false" hint="I handle.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to inject dependencies into.">
   <cfargument name="Object" type="any" required="yes" hint="I am the ob ject to inject dependencies into.">
      var DependentObjectName = "";
      // SETTER DEPENDENCIES       // If there are any setter dependencies       If (StructKeyExists(variables.Bean[arguments.ObjectName],"SetterDependencies"))
         // Inject them all          For (Count = 1; Count lte listlen(variables.Bean[arguments.ObjectName].SetterDependencies); Count = Count + 1)
            // Get current object name             DependentObjectName = ListGetAt(variables.Bean[arguments.ObjectName].SetterDependencies, Count);
            If (DependentObjectName NEQ arguments.ObjectName)
               void = evaluate("arguments.object.set#DependentObjectName#(variables.getSingleton(DependentObjectName))");   

      // MIXIN DEPENDENCIES       // If there are any mixin dependencies       If (StructKeyExists(variables.Bean[arguments.ObjectName],"MixinDependencies"))
         // Inject them all          For (Count = 1; Count lte listlen(variables.Bean[arguments.ObjectName].MixinDependencies); Count = Count + 1)
            // Get current object name             DependentObjectName = ListGetAt(variables.Bean[arguments.ObjectName].MixinDependencies, Count);
            If (DependentObjectName NEQ arguments.ObjectName)
               arguments.object.MixinObject(DependentObjectName, variables.getSingleton(DependentObjectName));         
   <cfreturn arguments.Object>

<cffunction name="MixinObject" returntype="void" access="public" output="false" hint="I add the passed object to the variables scope within this object.">
   <cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to add.">
   <cfargument name="Object" type="any" required="yes" hint="I am the object to add.">
   <cfset variables[ObjectName] = Object>

Handling Circular Setter and Mixin Dependencies
This is something I actually don't need to track. Because of the way my setter and mixin dependency injections work, they don't get stuck in a loop with circular dependencies, they just work!

So, plenty of testing required, but I feel I've learnt a lot writing LightWire, it only took a few hours and it still weighs in at under 300 lines of code in a single cfc. It also provides a programmatic config file, mixin injections, base class paths and a few other little enhancements that work really well for the way I program. Hey, it's not ColdSpring, but it is still kinda fun!

links of london's Gravatar Good article , i have read it!
# Posted By links of london | 7/27/09 12:02 AM
Rolex's Gravatar they are high quality timepieces which is attractive,<A href="://www.newstylerolex.com/" title="Cheap Brand Name

Watches">Rolex</A> -
<A href="://www.newstylerolex.com/" title="Replica Rolex">Replica Rolex</A> -
<A href="://www.newstylerolex.com/" title="Brand Name Watches">Replica Watches</A> -
<A href="://www.sunglassvip.net" title="Welcome to our Replica Watches website">Replica rolex</A> -
<A href="://www.sunglassvip.net" title="high quality timepieces">Rolex</A> -
<A href="://www.2g-3g.com" title="high quality timepieces">Rolex</A>
<A href="://www.watches-life.com" title="cheap price">Rolex</A>
<A href="://www.watches-life.com" title="cheap price">Replica Rolex</A>
<A href="://www.rolex-hot.com" title="cheaper price">Rolex</A>
<A href="://www.rolex-hot.com" title="cheaper price">Replica Rolex</A>
<A href="://www.vertuexclusiveshop.com" title="cheaper vertu">Vertu</A>
<A href="://www.vertuexclusiveshop.com" title="cheaper Replica vertu">Vertu Replica</A>
,good time!
# Posted By Rolex | 7/28/09 2:27 AM
ed hardy's Gravatar Thank you very much!
# Posted By ed hardy | 8/5/09 12:11 AM
Aion Power Leveling's Gravatar These online <A href="://www.power-leveling-fast.com/">Aion Power Leveling</A> sites are legal and they abide by <a href="://www.tiffany-mine.com">Tiffany</a> laid down rules and <A href="://www.powerleveling-services.com/">Aion gold</A> regulations.
# Posted By Aion Power Leveling | 12/31/09 1:56 AM
eve isk's Gravatar sell eve isk sell isk sell eve online isk
# Posted By eve isk | 4/5/10 6:14 AM
oyf1989's Gravatar <a href=://www.wowctmlvling.com/world-of-warcraft-us-buy-gold.html>wow cataclysm gold</a>
<a href=://www.wowctmlvling.com/world-of-warcraft-us-buy-gold.html>cheap cataclysm gold</a>
<a href=://www.wowctmlvling.com/world-of-warcraft-us-professional.html>cataclysm skills leveling</a>
# Posted By oyf1989 | 5/22/10 2:58 AM
nike dunk sheos's Gravatar <strong><a href="://www.shoesmass.com/nike-air-max-tailwind-2010-greyyellow-womens-p-582.html" title="Nike Air Max Tailwind 2010 (Grey/Yellow) Women's ">Nike Air Max Tailwind 2010 (Grey/Yellow) Women's </a></strong>

<strong><a href="://www.shoesmass.com/air-max-03-classic-white-midnight-navy-zen-grey-p-478.html" title="Air Max 03 Classic (white / midnight navy / zen grey)">Air Max 03 Classic (white / midnight navy / zen grey)</a></strong>

<strong><a href="://www.shoesmass.com/nike-air-max-2009-greyblackgreen-men-leather-p-526.html" title="Nike Air Max 2009 (Grey/Black/Green) Men Leather">Nike Air Max 2009 (Grey/Black/Green) Men Leather</a></strong>
# Posted By nike dunk sheos | 6/20/10 10:38 PM
ED Hardy cheap's Gravatar Thanks for enjoying this article.
This is awesome, please keep writing.
Good post!As i was passing by here and i read your post.
# Posted By ED Hardy cheap | 8/21/10 10:01 AM
manolo blahnik's Gravatar Remember the gorgeous blue Cinderella-like Manolo Blahnik wore at the end of the Sex and the City Movie?The classic design and the bold blue hue make them great contenders for your favorite formal footwear.
# Posted By manolo blahnik | 8/24/10 2:06 AM
jordan retro shoes's Gravatar I was very pleased to find this site. This is an intelligent and well written article, you must have put a fair amount of research into writing this.
# Posted By jordan retro shoes | 9/11/10 2:12 AM
r4 ds, prezzi orologi's Gravatar ://www.scheda-r4.com]DSTTi[/url]aggiungerà il supporto per la scheda.
Il provvedimento inviato al DSTT, è dotato di un gran numero di dispositivi periferici: un alto funzionario della pubblicità il lettore R4i DSI di schede SDHC, ://www.scheda-r4.com/ caricatore cellulari dual sim portatile, DSL e il GBA e il lato GBA delle carte
# Posted By r4 ds, prezzi orologi | 10/27/10 6:43 AM
Guojuicycoutureout's Gravatar ://www.outletfactory-coach.net coach factory outlet
://www.outletcoach20l2.net coach outlet online
://www.coachoutlet20l2.com coach outlet
://www.coachs2011.com coach outlet online
://www.juicycoutureoutlet2012.net juicy couture outlet online
://www.outletcoach20l2.net Coach outlet store online
://www.coachoutlet20l2.com Coach outlet online
://www.coachs2011.com coach outlet store online
# Posted By Guojuicycoutureout | 11/27/11 10:23 PM
sac louis vuitton pas cher's Gravatar sac louis vuitton pas cher
# Posted By sac louis vuitton pas cher | 12/25/11 6:48 PM
tiffany uk's Gravatar tiffany uk
# Posted By tiffany uk | 12/25/11 10:49 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.5.006. | Protected by Akismet | Blog with WordPress