Quartz.Net 3.0

I went poking around the Quartz.Net repository while trying to answer some related questions on StackOverflow. I noticed that there is now a quartznet-3 branch present (although it's apparently been there for a couple of months). I pulled the branch but I was unable to build it. It was interesting to see that some things have changed, so you should expect to hear more about those in the form of blog posts.

Are you interested in keep up with Quartz.Net v3 development? Then you have a couple of options:

  1. Set up the RSS feed in your news reader so that you can keep track of what's going on with that version branch.
  2. I'll write some posts as I find out interesting stuff to talk about, so stop by or subscribe to my news feed.

Running Windows 10 IoT on a Raspberry Pi

I've been working with Raspberry Pi hardware for a while now, but mostly using the tools that are more common in the space. That means that my devices have all been running Raspbian as the OS and that the programs I've written for them have all been written in Python. Now that the official release of Windows 10 for IoT is out, I thought I'd give it a whirl and talk about my experience.

Installation

Installation of Windows 10 for IoT is very straightforward. Simply follow the steps outlined here. The one caveat is that you have to do this from a PC that already has Windows 10 installed. This may or may not be an issue for you.

I managed to get through installation without a hitch and was able to boot the Raspberry Pi with no issues. I did not connect a keyboard or monitor to mine, I simply plugged it in to my home network via an ethernet switch.

Network Connectivity

Ethernet Port

Connecting the device using the ethernet port worked out of the box for me.

WIFI

I wasn't able to get the USB WIFI adapter I have to work with Windows 10 IoT on the Raspberry Pi. I've used the Edimax EW-7811Un "successfully" with the Raspberry Pi and Raspbian but it simply doesn't work with Windows 10 on the Raspberry Pi. The internet seems to think and agree that only the official WIFI USB dongle will work. It's the only one that is showing as being supported on the official Windows 10 for IoT page. I contacted Edimax and asked them if they had a driver and their reponse was that they don't have a driver available and that they don't have it on the roadmap to build one. I tried using this same adapter on my Surface RT running Windows 8.1 and that adapter is not supported there either.

Remote Desktop, SSH and PUTTY

Device Name and Credentials

Once you get your device on the network, the default name for your device will be minwinpc (thought I'd save you some time searching through your router's connected device table). The default username to connect is administrator and the default password is p@ssw0rd. You should change this once you're able to connect to your Raspberry Pi.

Remote Desktop

First I tried using Remote Desktop to connect to the Raspberry Pi. It didn't work. I searched around the internet for a while and couldn't find any indications that this is possible at this time. I did notice that the Remote Desktop service is installed and running, so maybe there is a way to enable remote connections. Perhaps it's simply disabled and with some PowerShell magic you could turn it on. I might come back to this later but didn't spend more time on it.

SSH

Next I tried connecting to it via SSH. I already had Cygwin installed on my PC, so I tried that first. It didn't work. Then I tried updating my SSH client on Cygwin. That didn't work either. From looking at the verbose SSH connection dialog, it seems there is a mismatch between some of the algorithms supported by the SSH client and the Windows IoT SSH server. I might try to get one of my linux friends to help me debug this, as it seems many folks are trying to do the same thing I was trying to do with about the same results I had.

PUTTY

Now, the "manual" says you should use PUTTY to SSH into the RPi. So, I downloaded PUTTY and lo and behold, it connected! Now I'm looking at a regular c:\ command prompt. Huh, that was interesting. So I went to my PC and mounted a network drive to the C drive on my RPi. It worked! I think there might be a WinIoT RPi NAS project in there somewhere. But that project will have to go on my project backlog.

What Now?

Well, now that I have this device up and running, it's time for me to look at which one of my projects to tackle next. Perhaps the best candidate is to port some of my existing Python based projects to Windows 10 and C# and see how that works. Or maybe see if I can get Quartz.Net to run on the Raspberry Pi. I'll write some more posts as I make progress.

Help Me Out If You Can

If you're able to find a USB adapter that works with Windows IoT on the Raspberry Pi (other than the "official" one) or if you're able to get remote desktop to work, please let me know!

Setting up Visual Studio Online and Hipchat Integration

While searching for a way to add TFS integration to Hipchat, I came across a bunch of posts that were not particularly useful. In my particular case, I was looking to integrate Visual Studio Online's TFS with Hipchat, so using a separate service to manage this integration seemed like unnecessary pain. So, I poked around a little in the Visual Studio Online portal and found that it is indeed quite easy to set this up. Here are the steps I followed, more as a reminder to myself for the next time I want to do this, but hopefully it will help others as well.

First, log in to your hipchat account and go to the home page. If you have already created a room that you want to integrate with, then this is where you will want to be and you can skip to the next paragraph. If you haven't created the room yet, you'll have to do that before you do anything else. And guess what, you can't do that from the group admin home page, so go ahead and launch the web app. Once in the chat app, create the room and go back to the home page.

Now that you have the room with which you want to integrate, from the home page, click on the rooms tab and then click on the room you want to integrate with. Now we need to create a token, so click on the tokens link and create a new token. You'll need this token for the next step.

That is all the setup that is required on the Hipchat side. Let's now log in to Visual Studio Online. Once you are at the home page, select the project you want to integrate with. We want to update the settings for the project, so click on the gear icon on the top right. Now, click on the Service Hooks tab and create a new service hook. Select Hipchat from the service list that comes up and then select the type of trigger you want. At some point you'll get asked for the token you created earlier. Copy that into the Authorization Token field and then finish up whatever else you need to in there. At the end you'll have the option to send a test message to confirm that everything is working.

That's it. No service to install or plugin to configure. I have to say it was much easier than I expected.

Introducing the Quartz.Net Feature Pack

I'd like to introduce the Quartz.Net feature pack project to you. It's hosted on GitHub and it's main purpose is to provide some features that are not currently part of Quartz.Net. Traditionally, Quartz.Net has been a direct port of the Java implementation of Quartz and so it doesn't typically add new functionality that might be useful if it is not part of the Java version.

Believe it or not, work on this project goes on, albeit slowly, because of the usual reasons. I did want to use this post to outline what is already there and also to keep a running list of things I'd like to see. These features are all tied to some sort of pain point that I've run into as I've been using the scheduler. Let's start with what is already there, in some shape or form.

What is Currently Available in the Quartz.Net FeaturePack

What is Not There Right Now But I'd Like to See

  • All of the above items finished (some are almost done, some are quite basic)
  • A way to dynamically reload job DLLs, so the scheduler doesn't have to be stopped and started whenever you update one of the jobs.
  • Cluster aware management tools
  • Nicer graphical interfaces. Yes, there are a few out there but they're scattered all over. Would be nice to have everything in one place.

I'm making a conscious effort this year to spend more time working on this project. If you have any suggestions on features that you would like to see added, send me an email or just head on over to the QuartzNetFeaturePack project page on Github and add an issue there.

Quartz.Net Built-In Listeners

This is the fifth post in the Quartz.Net Listener Tutorial series. In this post we'll be looking at the listeners that are shipped with the Quartz.Net distribution.

Scheduler Listeners

If we search through the Quartz.Net codebase we'll find two classes that implement the ISchedulerListener interface: SchedulerListenerSupport and BroadcastSchedulerListener. Let's look at these guys in detail now.

SchedulerListenerSupport

The documentation for the SchedulerListenerSupport says that this class is

A helpful abstract base class for implementors of ISchedulerListener

The methods in this class are empty so you only need to override the subset for the ISchedulerListener events you care about.

Well, there you have it. An empty class where all the methods of the interface are implemented but left empty. I guess this can save you some typing but I'm not sure it's very useful if you want to implement a listener. The way to use this is to inherit from this class and then only implement the methods you really are interested in.

BroadcastSchedulerListener

The documentation for the BroadcastSchedulerListener says that this class

Holds a List of references to SchedulerListener instances and broadcasts all events to them (in order).

This may be more convenient than registering all of the listeners directly with the Scheduler, and provides the flexibility of easily changing which listeners get notified.

If we were to hide all of the methods that are required by the ISchedulerListener interface, then we would be left with these two methods, which are at the heart of what this listener does:

public void AddListener(ISchedulerListener listener)
{
    listeners.Add(listener);
}

public bool RemoveListener(ISchedulerListener listener)
{
    return listeners.Remove(listener);
}

The listeners variable is a generic List, so it follows the semantics of that class. In essence this listener manages a list of listeners and then forwards all of the scheduler events to all the listeners. This is probably useful if you have a large number of scheduler listeners.

Now let's go hunting for some job listeners. I think we might find some interesting things there.

Job Listeners

After searching through the codebase for classes that implement IJobListener and classes that inherit from JobListenerSupport, I was able to find 4 job listeners: JobListenerSupport, BroadcastJobListener, JobChainingJobListener and LoggingJobHistoryPlugin. Let's look at these job listeners now.

JobListenerSupport

The documentation for JobListenerSupport states that this class is

A helpful abstract base class for implementors of IJobListener.

The methods in this class are empty so you only need to override the subset for the IJobListener events you care about.

You are required to implement IJobListener.Name to return the unique name of your IJobListener.

As you can see, it's a class similar to the SchedulerListenerSupport class we reviewed earlier. It's an empty implementation, so perhaps useful if you're only interested in some of the methods and want to save some keystrokes. It also includes a logger instance in it. The JobChainingJobListener that we're about to discuss inherits from this instead of implementing the IJobListener interface directly.

BroadcastJobListener

The documentation for BroadcastJobListener states that this class

Holds a List of references to JobListener instances and broadcasts all events to them (in order).

The broadcasting behavior of this listener to delegate listeners may be more convenient than registering all of the listeners directly with the Scheduler, and provides the flexibility of easily changing which listeners get notified.

This class is also similar to the BroadcastSchedulerListener we went over. Similarly, it provides just a few methods in addition to the ones required by the interface:

public void AddListener(IJobListener listener)
{
    listeners.Add(listener);
}

public bool RemoveListener(IJobListener listener)
{
    return listeners.Remove(listener);
}

public bool RemoveListener(string listenerName)
{
    IJobListener listener = listeners.Find(x => x.Name == listenerName);
    if (listener != null)
    {
        listeners.Remove(listener);
        return true;
    }
    return false;
}

Like its scheduler counterpart, this broadcast listener forwards all of the job events to all the registered job listeners. If you have lots of job listeners, this could be useful.

JobChainingJobListener

The documentation for JobChainingJobListener states that this class

Keeps a collection of mappings of which Job to trigger after the completion of a given job. If this listener is notified of a job completing that has a mapping, then it will then attempt to trigger the follow-up job. This achieves "job chaining", or a "poor man's workflow".

Generally an instance of this listener would be registered as a global job listener, rather than being registered directly to a given job.

If for some reason there is a failure creating the trigger for the follow-up job (which would generally only be caused by a rare serious failure in the system, or the non-existence of the follow-up job), an error messsage is logged, but no other action is taken. If you need more rigorous handling of the error, consider scheduling the triggering of the flow-up job within your job itself.

Kind of cool no? A simple way to chain jobs is provided in the box for you. It's not a fully developed workflow engine but for sequential jobs it probably does the trick. Let's look at the two methods of interest in this job listener. Here they are:

public void AddJobChainLink(JobKey firstJob, JobKey secondJob)
{
    if (firstJob == null || secondJob == null)
    {
        throw new ArgumentException("Key cannot be null!");
    }
    if (firstJob.Name == null || secondJob.Name == null)
    {
        throw new ArgumentException("Key cannot have a null name!");
    }

    chainLinks.Add(firstJob, secondJob);
}

public override void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
    JobKey sj;
    chainLinks.TryGetValue(context.JobDetail.Key, out sj);

    if (sj == null)
    {
        return;
    }

    Log.Info(string.Format(CultureInfo.InvariantCulture, "Job '{0}' will now chain to Job '{1}'", context.JobDetail.Key, sj));
    try
    {
        context.Scheduler.TriggerJob(sj);
    }
    catch (SchedulerException se)
    {
        Log.Error(string.Format(CultureInfo.InvariantCulture, "Error encountered during chaining to Job '{0}'", sj), se);
    }
}

The first method, AddJobChainLink, is the method we would call to set up the links. That's all we need to do to set up a chain: tell the scheduler which job should fire (secondJob) after the first job completes (firstJob). The JobWasExecuted method is called by the scheduler once a job has finished running. If the listener finds a link for the job that was executed, it will trigger the new job immediately.

This is one of those features that not many people know about but it comes up every now and then on the mailing list.

LoggingJobHistoryPlugin

The documentation for LoggingJobHistoryPlugin states that this class

Logs a history of all job executions (and execution vetos) via common logging.

The logged message is customizable by setting one of the following message properties to a string that conforms to the syntax of string.Format(string,object).

JobToBeFiredMessage - available message data are:

Element Data Type Description
0 String The Job's Name.
1 String The Job's Group.
2 Date The current time.
3 String The Trigger's name.
4 String The Triggers's group.
5 Date The scheduled fire time.
6 Date The next scheduled fire time.
7 Integer The re-fire count from the JobExecutionContext.

The default message text is "Job {1}.{0} fired (by trigger {4}.{3}) at: {2:HH:mm:ss MM/dd/yyyy}"

JobSuccessMessage - available message data are:

Element Data Type Description
0 String The Job's Name.
1 String The Job's Group.
2 Date The current time.
3 String The Trigger's name.
4 String The Triggers's group.
5 Date The scheduled fire time.
6 Date The next scheduled fire time.
7 Integer The re-fire count from the JobExecutionContext.
8 Object The string value (toString() having been called) of the result (if any) that the Job set on the JobExecutionContext, with on it. "NULL" if no result was set.

The default message text is "Job {1}.{0} execution complete at {2:HH:mm:ss MM/dd/yyyy} and reports: {8}"

JobFailedMessage - available message data are:

Element Data Type Description
0 String The Job's Name.
1 String The Job's Group.
2 Date The current time.
3 String The Trigger's name.
4 String The Triggers's group.
5 Date The scheduled fire time.
6 Date The next scheduled fire time.
7 Integer The re-fire count from the JobExecutionContext.
8 String The message from the thrown JobExecution Exception.

The default message text is "Job {1}.{0} execution failed at {2:HH:mm:ss MM/dd/yyyy} and reports: {8}"

JobWasVetoedMessage - available message data are:

Element Data Type Description
0 String The Job's Name.
1 String The Job's Group.
2 Date The current time.
3 String The Trigger's name.
4 String The Triggers's group.
5 Date The scheduled fire time.
6 Date The next scheduled fire time.
7 Integer The re-fire count from the JobExecutionContext.

The default message text is "Job {1}.{0} was vetoed. It was to be fired (by trigger {4}.{3}) at: {2:HH:mm:ss MM/dd/yyyy}"

The documentation is pretty extensive but the bottom line is that it's a listener that logs all of the job related events to the common.logging infrastructure. It's pretty useful if you're not sure why jobs aren't firing when you expect them to and to get a general idea of what the scheduler is doing.

The one thing that might strike you as odd is that it's also a scheduler plugin. This is so that you can add the job listener through the configuration file upon scheduler startup so that you don't miss any events. There are a couple of ways of adding listeners to a scheduler, but I'll adress that in a different post.

Trigger Listeners

The last set of built-in listeners that we'll take a look at are the trigger listeners. Let's search the codebase for ITriggerListener and see what we find. I only found 2 classes that implement the ITriggerListener interface: TriggerListenerSupport and BroadcastTriggerListener. Just like we did for job and scheduler listeners, let's go a little deeper and talk about these classes a bit.

TriggerListenerSupport

The documentation for the TriggerListenerSupport says that this class is

A helpful abstract base class for implementors of ITriggerListener.

The methods in this class are empty so you only need to override the subset for the ITriggerListener events you care about.

You are required to implement ITriggerListener.Name to return the unique name of your ITriggerListener.

This is the third time we've seen a description such as this one, and I think that by now you get the idea. This is, for the most part, an empty class. There's a logger there and the VetoJobExecution will always return false.

BroadcastTriggerListener

The documentation for the BroadcastTriggerListener says that this class

Holds a List of references to TriggerListener instances and broadcasts all events to them (in order).

The broadcasting behavior of this listener to delegate listeners may be more convenient than registering all of the listeners directly with the Scheduler, and provides the flexibility of easily changing which listeners get notified.

You've seen this before, yes? It's another broadcasting implementation that forwards trigger events to trigger listeners that are registered with it.

What was that all about?

We've just gone over all of the listeners that come with the Quartz.Net distribution. Now you know that there are empty implementations of all 3 listeners that you can inherit from if you only want to implement a few of the methods required by the interface. You also know that there are 3 broadcasting listeners that you can use if you have large numbers of listeners that you want to manage a bit easier. Finally, we found a couple so interesting job listener classes that could come in handy if you wanted to get more detailed information on what was going on inside the scheduler (LoggingJobHistoryPlugin) or if you want to link together multiple jobs, so that they execute in sequence (JobChainingJobListener).

I hope this is useful information that may come in handy some day. If you wish to comment on this post, send me an email and I'll attach your comment to this post.