Powered by discountASP.NET
referal ID: sdtref
Why recommend discountASP.NET?

Archives
Steve Trefethen Steve's RSS Feed Subscribe or via email
What's this?
Contact me Send mail to the author(s)
About Me
View my LinkedIn profile

Add to Google
Subscribe with Bloglines
MCP Microsoft Certified Professional

Falafel Software
ActiveFocus Project Management Solution by Falafel Software
Online or OnSite TestComplete Training
Blogroll
Recent Comments
My Online Tools
Stats
Total Posts: 441
This Year: 46
This Month: 2
This Week: 0
Comments: 1526
Tags
Disclaimer
The posts on this weblog are provided �AS IS� with no warranties, and confer no rights. The opinions expressed herein are my own personal opinions and do not represent my employer�s view in any way.
 Tuesday, March 04, 2008

CCNET based EDI Invoicing Project Goes into Production

Posted @ 12:50AM by Steve Trefethen

Categories: Continuous Integration | Development | Open Source

Tags:  |  | 

Over the past six months I’ve been working on a system to automate invoicing using EDI. The system is built on CruiseControl.NET and a collection of custom written source control providers and CCNET tasks. Back in December, I blogged about this project which integrates with a custom ERP system built from scratch by Falafel that processes half a billion dollars worth of transactions a year so it’s nice to finally see my small part come online.

There are several Open Source projects used in the implementation including:

Here’s a diagram of the system with CCNET at the heart:

EDI Invoicing using CruiseControl.NET

One of the many benefits of using CruiseControl.NET is the visibility the system can provide to the people in Accounts Receivable. Not only can they see what’s going on via the CCNET web dashboard but they can even install CCTray and monitor from their desktop as invoices are processed throughout the day. On the web dashboard clicking through to a specific "build" log for a given EDI 810 "build" provides invoice details including invoice number, customer name, date and dollar amount.

EDI CCNET Web Dashboard
As you can see I still need to customize the dashboard to fit into the web-based ERP system’s look and feel.

CCNET’s plug-in architecture made it perfect for this situation as it has numerous features that can be leveraged to create this, perhaps unusual, usage of a Continuous Integration server. I suppose CI could just as easily stand for Continuous Invoicing. :-)

EDI 850 Up Next

My next task will be to re-work on an existing, read old, EDI 850 Purchase Order set of processes and integrate them into CCNET. Anyway, this has been a fun project to work on and I’ve learned a lot along the way and I’d like to give a tip 'O the hat to John Waters, Falafel’s CTO, for all his help. If you’re looking for an ERP guru, I think you’d be hard pressed to top John plus he’s got a great sense of humor. One afternoon while driving back through the farm land of Watsonville from our client’s site, one of the largest organic food companies in the world, he quipped:

Here we go driving through our business objects...

Something only another geek could really appreciate!

Lastly, I have to admit after 15 years of doing product development feels very satisfying to design and implement a system that solves a real world business problem. There’s just no substitute for hands on experience!

Related posts:

[UPDATED: March 4, 2008] Forgot SubSonic which I use for data mapping/xml creation!
 Wednesday, February 06, 2008

An FTP Source Control Provider for CruiseControl.NET

Posted @ 12:20AM by Steve Trefethen

Categories: ccnet | Continuous Integration | Open Source

Tags:  |  | 

I've been working on a custom CCNET implementation to handle EDI invoicing which is nearly complete and one of the things I needed was FTP download support. In the project I'm working there is a utility that makes use of edtFTPnet, from Enterprise Distributed Technologies, an Open Source C# FTP library licensed under LGPL. It seems to work reasonably well though the authors are admittedly "love the 'internal'" keyword thus making extensibility difficult, if not impossible, short of forking the codebase. Anyway, I've been using the library and it seems to work just fine. Btw, I've submitted my suggested changes so hopefully they'll recognize the benefits and incorporate them.

Writing Plugins for CruiseControl.NET

Anyway, what I needed as a mechanism that would poll an FTP server looking for new files to be downloaded which sounded like a reasonable thing for CruiseControl.NET. Of course, once files are downloaded additional processing is done so it fits with CI quite nicely. Fortunately, CruiseControl.NET has a nice plugin architecture that makes this sort of thing easy.

Creating a Plugin Assembly

The first step is to create a new Class Library following the naming convention ccnet.*.plugin.dll and add the following assembly references:

  • edtFtpnet
  • Thoughtworks.CruiseControl.Core.dll
  • NetReflector.dll

And add the following to your using statements:

  • System.Collections
  • EnterpriseDT.Net.Ftp
  • Exortech.NetReflector
  • ThoughtWorks.CruiseControl.Core
  • ThoughtWorks.CruiseControl.Core.Util

Your assembly should appear as follows (click the image for a larger version):

CCNET plugin assembly

Adding ISourceControl

The next step is to add the ISourceControl interface and provide the necessary attribute which will declare the name used to identify your Source Control Provider.

[ReflectorType("ftpscc")]
public class FTP: ISourceControl

The ReflectorType attribute comes from the NetReflector assembly and is located in the Exortech.NetReflector namespace. The value provided to the attribute is the name which will be used in ccnet.config as follows:

<sourcecontrol name="ftpscc"></sourcecontrol>

Adding FTP Specific Properties

Next, add properties for the FTP connection and once again we'll use attributes from Exortech.NetReflector making it easy to declare settings from ccnet.config:

[ReflectorProperty("deleteRemoteFiles", Required = false)]
public bool DeleteRemoteFiles = true;

[ReflectorProperty(
"fileMask")]
public string FileMask;

[ReflectorProperty(
"localPath")]
public string LocalPath;

[ReflectorProperty(
"password")]
public string Password;

[ReflectorProperty(
"remoteHost")]
public string RemoteHost;

[ReflectorProperty(
"timeOut", Required = false)]
public int TimeOut = 60;

[ReflectorProperty(
"userName")]
public string UserName;

[ReflectorProperty(
"remoteDir", Required = false)]
public string RemoteDir = "/";

These properties indicate settings for the FTP connection as well as the location where the files should be placed after being downloaded. Additionally, there is a flag which indicates if the downloaded file should be deleted from the server.

Again, simply adding the ReflectorProperty attribute will cause these members to be populated with the data from your <sourcecontrol name="ftpscc"> tag in ccnet.config.

Implementing ISourceControl

Next, implement the ISourceControl interface on your class. Of course, the easiest way (in VS.NET) to stub out the necessary methods is via the context menu with the caret sitting on ISourceControl:

VS.NET Implement Interface

This will give you five new methods though I'll focus on the following two since that's all you really need to implement here:

public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)

public void GetSource(IIntegrationResult result)

In GetModifications we need to return an array which contains information about the files to be downloaded and in GetSource actually fetch the files. One caveat here is we use DateTime.Now as the modified time so that CCNET will consider the file as a "new" change. So, GetModifications will look like this:

1 public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)
2 {
3 ArrayList mods = new ArrayList();
4 Connect();
5 try 6 {
7 Log.Debug("Fetching list of files that match: " + FileMask);
8 FTPFile[] mModifiedFiles = ftp.DirDetails(FileMask);
9 Modification mod;
10 int i = 0;
11 foreach (FTPFile f in mModifiedFiles)
12 {
13 mod = new Modification();
14 mod.Type = "Added";
15 mod.FileName = f.Name;
16 mod.ChangeNumber = i;
17 mod.ModifiedTime = DateTime.Today;
18 mods.Add(mod);
19 i++;
20 }
21 return (Modification[])mods.ToArray(typeof(Modification));
22 }
23 finally 24 {
25 // Close the connection if there are no files to process 26 if (ftp.IsConnected && mods.Count == 0)
27 ftp.Quit();
28 }
29 }
30

GetSource handles downloading files from the server and then deleting them:

1 public void GetSource(IIntegrationResult result)
2 {
3 try 4 {
5 foreach (Modification m in result.Modifications)
6 {
7 Log.Debug("Fetching file: " + m.FileName);
8 ftp.Get(LocalPath, m.FileName);
9 if (DeleteRemoteFiles)
10 {
11 Log.Debug("Deleting remote file: " + m.FileName);
12 ftp.Delete(m.FileName);
13 }
14 result.AddTaskResult(string.Format(
15 "<fileDownloaded>{0}</fileDownloaded>\n", m.FileName));
16 }
17 }
18 finally 19 {
20 ftp.Quit();
21 }
22 }

Connecting to the FTP Server

The call to Connect in GetModifications above looks like this:

1 public void Connect()
2 {
3 Log.Debug(String.Format("Connecting to remotehost: {0}...", RemoteHost));
4 ftp.Connect();
5 Log.Debug(String.Format("Connected, logging in with user: {0}", UserName));
6 ftp.Login(UserName, Password);
7 Log.Debug(String.Format("Switching to remote directory: {0}", RemoteDir));
8 ftp.ChDir(RemoteDir);
9 }

The only thing I haven't really covered here is the construction of the FTPClient member and the assignment of the its RemoteHost and Timeout properties which I think is pretty straightforward.

Conclusion

Writing plugins, whether for Source Control Providers or tasks (ITask), the process is pretty straightforward though there is little documentation on the subject. Be prepared to spend some time studying the other ISourceControl and ITask implementations included with CCNET and quite a bit of time debugging CCNET itself to understand how it works. One issue I've run into and that I've blogged about before which is when exceptions occur from within a Source Control Provider. In the current released version of CCNET (1.3) exceptions are not handled by the publishers for the project therefore you'll never get notified about connection problems etc. unless you use the log4net settings I mentioned.

The above implementation is lacking in a number of areas, like error checking, and is a very simplified example of code I'm using in production. I've also tweaked CCNET itself so that any exceptions raised within my FTP provider are handled by the build publisher so that I can get email when things fail. In fact, I've been working to get these, or similar, changes incorporated into CCNET.

[UPDATE: Feb 6, 2008] Fix image links
 Monday, January 28, 2008

Using the Delphi command line compiler on a Continuous Integration server

Posted @ 11:25AM by Steve Trefethen

Categories: Continuous Integration | howto

Tags:  | 

Recently, I got an email asking how to deploy the Delphi command line compiler for use on a continuous integration (CI) server. Since I’m no longer working at CodeGear nor coding in Delphi I forwarded the message to a few guys at CodeGear mentioning that it would be a good blog post but alas, I never got a response. Geeze, I didn’t think it had been that long guys! :-) Anyway...

I figured the question was worth a post if for no other reason than to start a conversation so other Delphi developers could contribute with their experience. Before we begin be sure to read your Delphi software license as I seem to recall there being some sort of issue related to the license regarding compiler deployment.

Getting the command line compiler to a build machine should be pretty straight forward though I’ve never had a need to do it myself since I was always working with the entire source tree including the compiler.

Basically, what’s needed is the Delphi compiler and the DCU/DCP’s from the products "lib" directory which means.

  • DCC32.EXE
  • The lib directory from your Delphi IDE source path

Aside from that here are some other suggestions for a build machine:

  • Using command line options (or a dcc32.cfg file) always specify an output path for the compiler, never place project related output in the same directory as DCU/DCP’s that are shipped with Delphi
  • Make sure you do a "clean" before each new build meaning run a process that deletes all of the compiler produced output from the previous build and verify that it actually works!

Be sure to read Language Reference Guide as I recall working with Nathan Tawill years ago on it and it was very good.

Lastly, another option would be to simply install a copy of the product on the build machine. Oh yeah, be sure to ask CodeGear to create an install that will take care of all this for you.

See also:
Other continuous integration posts
Video: Setting up a continuous integration environment

Ok, that’s my quick and dirty write up, your turn, what’s missing?
 Wednesday, January 16, 2008

CruiseControl.NET features beyond v1.3

Posted @ 8:03AM by Steve Trefethen

Categories: Continuous Integration | Open Source

Tags:  | 

The last official release of CruiseControl.NET was v1.3 from June 2007 but that doesn’t mean there hasn’t been ongoing work. I’ve been updating my CCNET sources recently and looking at some of the new features which include:
  • devenv task updated to VS.NET 2008
  • ExternalSourceControl source control provider
    <sourcecontrol type="external"> <executable>(path to command-line application)</executable> <args>(command-line parameters for application</args> <autoGetSource>(true/false like other sourcecontrol providers)</autoGetSource> <labelOnSuccess>(true/false like other sourcecontrol providers)</labelOnSuccess> <environment> <var>name=value</var> (environment variables to pass to the provider) <var>name=value</var> </environment> </sourcecontrol>
  • FilteredSourceControl block for specific comments / labels
  • Expand notification of modifying users to support more than one status (e.g., "Failed" + "Fixed" so you get both the bad news and the good news).
  • PlasticSCM source control support
  • Improved standard statistics report page
  • RSS feed for build status
  • RegEx support in the email publisher to allow for utilizing source control usernames as email addresses among other things
  • Lots of bug fixes in CCNET as well as CCTray
These are just some of the items I thought worth noting while perusing the SVN change log. There are lots of other changes, fixes etc. If you’re looking for CCNET improvements you might want to try and grab the source. I found building the tree to be quick and painless.
 Friday, January 04, 2008

Exceptions in CruiseControl.NET revisited

Posted @ 1:42PM by Steve Trefethen

Categories: ccnet | Continuous Integration

Tags:  | 

The other day I wrote about configuring CruiseControl.NET to send email when an exception occurred at the project level, meaning outside of the actual "build" which technically equates to the <tasks> block. I’ve since spent some time trying to understand why the CCNet was changed to remove support for PublishExceptions and found revision 2567 to IntegrationRunner.cs where it was removed with a comment of "all exceptions during build break the build
deprecating PublishExceptions attribute". That didn’t exactly provide the explanation I was looking for and AFAICT, this change has now left a gap in CruiseControl.NET where exceptions occurring outside of the <tasks> block can’t be published using a build publisher. So, unless you’re checking the log files regularly, or you’ve configured log4net using an SmtpAppender they’ll go unnoticed.

To figure this out I’ve been posting messages to the ccnet-devel group and thankfully I’m getting some response although it’s been a very slow conversation that’s had it’s share of missteps due, in part, to my incorrect phrasing of where exceptions were occurring. Originally, I was looking for guidance thinking I must be missing something. After a few more posts a change request and subsequent revision was made but the change addressed a situation I’m pretty sure can’t occur in the current code base short of a plug-in explicitly catching exceptions and setting the build status to "Exception" which isn’t what I was looking for.

I believe the necessary change to allow for integration exceptions to be published looks something like this (from IntegrationRunner.cs):

1 public IIntegrationResult Integrate(IntegrationRequest request)
2 {
3 IIntegrationResult result = resultManager.StartNewIntegration(request);
4 IIntegrationResult lastResult = resultManager.LastIntegrationResult;
5 try 6 {
7 CreateDirectoryIfItDoesntExist(result.WorkingDirectory);
8 CreateDirectoryIfItDoesntExist(result.ArtifactDirectory);
9 result.MarkStartTime();
10 result.Modifications = GetModifications(lastResult, result);
11 if (result.ShouldRunBuild())
12 {
13 Log.Info("Building: " + request);
14 Build(result);
15 PostBuild(result
16 Log.Info(string.Format("Integration complete: {0} - {1}",
17 result.Status, result.EndTime));
18 }
19 target.Activity = ProjectActivity.Sleeping;
20 return result;
21 }
22 catch (Exception ex)
23 {
24 result.Status = IntegrationStatus.Exception;
25 result.ExceptionResult = ex;
26 target.PublishResults(result);
27 return result;
28 }
29 }
30

I’ve added a try...catch to handle exceptions that occur during the integration process and call PostBuild() which calls FinishIntegration() and PublishResults(). That way any exception occurring during the integration process can be handled by any of the available publishers including the email publisher. Btw, the old PublishExceptions code looked similar. Of course, the code presented here isn’t the only, nor perhaps even the best way to handle this situation personally, I’m not fond of PostBuild() appearing twice but adding a finally block seemed to unnecessarily complicate the code. Since I don’t understand the reasoning behind removing the PublishExceptions tag it’s hard to know what the desired behavior here is but I’m pretty sure what’s checked in is probably not what was intended.

There is one other change I think is important as well which is to modify IntegrationResult.ShouldRunBuild() as follows (from IntegrationResult.cs):

1 public bool ShouldRunBuild()
2 {
3 return status == IntegrationStatus.Unknown &&
4 (BuildCondition.ForceBuild == BuildCondition || HasModifications());
5 } 

This change rather subtle but it allows for an ISourceControl plug-in to handle it’s own exceptions and still prevent the build from executing by setting IntegrationStatus to something other than "Unknown". Without the check for IntegrationStatus.Unknown the build would run regardless of exceptions that occurred within the GetModifications() call. For example, here is a catch block you could use within your implementation of ISourceControl.GetModfications() to prevent the build from executing. However, this will only work if the above proposed changes are made to CCNet.

1 catch(Exception ex)
2 {
3 to.ExceptionResult = ex;
4 to.Status = ThoughtWorks.CruiseControl.Remote.IntegrationStatus.Exception;
5 return null;
6 }

In the event of an exception you’ll have the opportunity to have the exception published using your configured publishers. Additionally, the web dashboard will reflect the fact that the build is left in an Exception state.

Anyway, I’m going continue to work this issue and see if I can get either the above changes or some other solution integrated into CCNet though the thread on the groups seems to have died.

[UPDATE: Jan 4, 2008] Here is an SVN patch for these changes.
[UPDATE: Jan 9, 2008] Fix for IntegrationRunner.Integrate to simply publish the results in the event of an exception.
 Thursday, December 13, 2007

Configuring CruiseControl.NET to publish exceptions outside of build tasks

Posted @ 12:22AM by Steve Trefethen

Categories: Continuous Integration | howto | Open Source

Tags:  |  | 

The other day I blogged about plug-ins I’ve been writing for CruiseControl.NET for a custom process that I’m working on. I’ve written a custom <sourcecontrol> plug-in that monitors a directory on an FTP server for new files. If files are present they’re downloaded and a build is triggered. The <sourcecontrol> tag of my ccnet.config file section looks like this:
<sourcecontrol type="ftp"> <remoteHost>localhost</remoteHost> <userName>User</userName> <password>pwd</password> <localPath>c:\temp\</localPath> <remoteDir>out</remoteDir> <timeOut>2</timeOut> <fileMask>*.*</fileMask> </sourcecontrol>

A problem I was running into was that exceptions, like failed FTP connections, within my sourcecontrol plug-in weren’t being published using my email publisher as configured in ccnet.config. Prior to v1.1 this used to work using if you had following tag in your ccnet.config file:

<publishExceptions>true</publishExceptions>

With v1.1 came support for log4net replacing the functionality of this tag which is mentioned here but without further details as to the alternative. To publish these exceptions you have to configure a log4net SmtpAppender to sent email which can be done from app.config as follows:

<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender"> <to value="to@b.com" /> <from value="from@b.com" /> <subject value="test logging message" /> <smtpHost value="smtpserver" /> <bufferSize value="512" /> <lossy value="true" /> <evaluator type="log4net.Core.LevelEvaluator"> <threshold value="WARN"/> </evaluator> <layout type="log4net.Layout.PatternLayout"> <conversionPattern
value="%newline%date [%thread] %-5level %logger [%property{NDC}] -
%message%newline%newline%newline"
/> </layout> </appender>

Btw, don’t forget to configure this appender in the <root> node of the <log4net> section as well. It’s a bit of a bummer that you have to have two Smtp setups to handle these kinds of exceptions but at least you have the capability to get the information published.

[Update: Dec 19, 2007] To clarify the exception I'm referring to above is occurs in my implementation of ISourceControl.GetModifications.

 Sunday, November 25, 2007

Configuring email notifications for CruiseControl.NET

Posted @ 9:15PM by Steve Trefethen

Categories: Continuous Integration | howto

Tags:  | 

I gotten a few emails asking how I to setup email notifications for builds running under CruiseControl.NET. Here is a section from one of the ccnet.config files I’m using on a CruiseControl.NET server:
1 <publishers> 2 <statistics /> 3 <xmllogger /> 4 5 <email from="fromaddr@here.com" mailhost="192.168.1.14" 6 mailhostPassword="pwd" mailhostUsername="username" 7 includeDetails="true"> 8 <users> 9 <user name="Steve Trefethen" address="stevet@nospam.com" 10 group="NotifyGroup" /> 11 </users> 12 <groups> 13 <group name="NotifyGroup" notification="failed" /> 14 </groups> 15 </email> 16 </publishers>
Nothing unusual except of course for changing some of the more obvious details. :-)
 Sunday, November 18, 2007

Organizing your build process

Posted @ 10:42PM by Steve Trefethen

Categories: Automation | Continuous Integration | Development | Quality | Testing | Tips

Tags:  |  |  |  |  | 

In this, my second post in a series on automated testing, I’m going to talk about a few steps you’ll need to take after your team has committed to automation. Diving into the deep end and immediately writing a bunch of tests isn’t the place to start. Organizing your project, preparing your code base and planning for automation are the first priorities. Of course, the assumption here is that you’re adding automation after the fact.

Organizing your build

If your project isn’t easy to build you’ve identified the first thing that needs fixing. Having a repeatable automated build is key to a successful test automation strategy. Essentially, getting your build organized means the following two things:

  • Version control
  • Continuous Integration

Version Control

Subversion
The first step to organizing your build and preparing it for continuous integration is to make sure it’s under version control. There are lots of ways to implement version control but it’s the first step to repeatability which is what test automation is all about. Personally, I really like SubVersion otherwise known as SVN, and would highly recommend it particularly if you’re just starting out. There’s been plenty written about the benefits of version control so I won’t go into that here just make your choice and get your code checked in.

Build Automation

The next step is automating your build process. Jeff Atwood wrote The F5 Key Is Not a Build Process discussing the benefits of moving your build process beyond your IDE of choice and I couldn’t agree more. Build automation is really going to be the key to successful test automation. When changes are committed to your repository a build gets kicked off and subsequently launches your test automation. With this setup you’re automation is guaranteed to run against every change to your repository immediately notifying you when a change has "broken" the build.

Note: Make sure your team understands that a break in test automation that’s kicked off as part your continuous integration process is as bad as checking in a syntax error. Yeah, read that again. Even if the code builds, if the smoke test fails as a result of the check-in it should be treated as though a syntax error were checked in.
cruisecontrol.net
For continuous integration I’m a fan of CruiseControl.NET but as with source control you have a lot of choices. CruiseControl.NET is open source and includes a web dashboard that’s easy to modify and supports writing plugins making it easy to extend the build system. Its rather light on documentation so if you don’t want get your hands a little dirty I’d recommend something like Automated Build Studio from AutomatedQA.

 

Putting it all Together

If you find this to be a bit daunting, have no fear I’ve put together a 10 minute video that demonstrates this entire process from beginning to end for a simple project. Of course, your project will be more complex but you’ll get a feel for how easy it is to get going. Note, I made this video in April '07 while still employed at Borland which is no longer the case nonetheless the video is still relevant.

Previous entries in this series:

Other related posts I’ve written:

 Tuesday, October 23, 2007

Solution file warning MSB4051 GUID was not found in the .SLN file

Posted @ 11:35AM by Steve Trefethen

Categories: .NET | Continuous Integration | Development

Tags:  |  | 

We’re using CruiseControl.NET with an MSBuild task to build the solution file for a large ERP system which includes 21 .csproj files and recently when some code was moved to a new project the build started failing with this error:
Velocity.sln : Solution file warning MSB4051: Project {958E0376-0272-4149-A1CF-E03521D12A72} 
is referencing a project with GUID {14F4138C-4DA7-4029-A8D3-B1B3954C2839},
but a project with this GUID was not found in the .SLN file.

The weird thing is that from within VS.NET the project would build just fine. It turns out that the .sln file was missing "EndProject" line just above the GUID mentioned. Here is the fragment of the .sln file with the problem (fyi, I’ve wrapped the two project lines):

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VelocityProductionPlanning", 
"VelocityProductionPlanning\VelocityProductionPlanning.csproj",
"{4BAFD59F-EBB5-4FDA-8639-F7FC63F7F351}"
ProjectSection(WebsiteProperties) = preProject
  Debug.AspNetCompiler.Debug = "True"
  Release.AspNetCompiler.Debug = "False"
EndProjectSection
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VelocityLibraryMessaging",
"VelocityLibraryMessaging\VelocityLibraryMessaging.csproj",
"{14F4138C-4DA7-4029-A8D3-B1B3954C2839}"
EndProject

Notice, the project VelocityProductPlanning has no "EndProject" line. That caused MSBuild to generate the above warning and subsequently the build to fail.

Hope this helps.
 Tuesday, July 31, 2007