November 2008 Archive
Updating your hosts file in Vista 64-bit
I came across an interesting issue this morning whereby my hosts file had simply vanished. For the unitiated amongst you, this file presents a kind of local DNS for Windows TCP/IP which essentially means you can make up a domain and point it at an IP address and have that apply to your machine only.
Now, normally this file is located in systemroot\system32\drivers\etc where systemroot is C:\Windows. However, on 64-bit, System32 is replaced by SysWOW64. Not such a big issue, except when you go looking for your hosts file from inside a 32-bit application such as Flex Builder / Eclipse.
For some reason, in Vista 64-bit, 32-bit applications can’t see the 64-bit tree, meaning that the hosts file is essentially invisible. So, how the hell do we get round this?
Well, backdoor ahoy – there is a solution. In your C:\Windows folder (%systemroot%) create a “sysnative” folder and browse through that. Bingo! your good old 32-bit folder structure should now be present, and low and behold your hosts file.
Posted by Neil Middleton on 03 Nov 2008
PrintQueue - PDF printing with Acrobat Reader end to end solution
Last week I posted twice on printing PDF documents. This is the last post of this unplanned series. Today I would like to present an end to end solution including ColdFusion PrintQueue event gateway with two C# applications for controlling Acrobat Reader. Quick summary of the problem we faced last week: ColdFusion cfprint tag has problems when printing documents created with LiveCycle Designer. Not all items from documents, specially repeated regions are printed. At Monochrome we had to figure out how to print them. Simplest solutions are best and we decided to go with Acrobat Reader batch printing capabilities.
Previous articles can be found here:
- LiveCycle Designer + cfpdfform + cfpdf + cfprint = fail
- rinting PDF files created with LiveCycle designer
Acrobat Reader solution seems to work really well, there is only one very specific problem. It appears there is about 70 users printing documents very often. Unfortunately Acrobat Reader is not closing documents after sending them to the printer. We had to add component which will shutdown Acrobat Reader once it does so. This is causing next problem: how to make sure Acrobat Reader is always opened? First implementation of our solution used queue which allowed printing just one document at a time. That was because of how our component for killing Acrobat Reader was designed. It was looking for AcroRd32 process running on the server and when found it was killing it. When multiple AcroRd32 processes were running they were killed as well. Because of that there was no reason to run multiple Acrobat Reader instances in different threads. Friday last week we developed second PrintQueue version. This time it is an event gateway with two separate C# components. This time it uses process ID instead of process name.
Lets take a look at first C# app – AcrobatReaderStartPid.exe:
[sourcecode language="csharp"]
using System;
using System.Diagnostics;
namespace AcrobatReaderStartPid {
class Program {
static void Main(string[] args) {
ProcessStartInfo psi = new ProcessStartInfo();
psi.Arguments = "/n /t \"" + args[1] + "\" \"" + args[2] + "\"";
psi.FileName = args[0];
Process p = Process.Start(psi);
Console.WriteLine(p.Id);
}
}
}
[/sourcecode]
It is very simple code. It takes 3 arguments in following order:
- Full path to Acrobat Reader executable
- Full path to PDF file that will be sent to the printer
- Printer name, may be network name
The purpose of the code is to start Acrobat Reader process, read process ID and write it to the console. Event gateway reads it and passes it to second C# component – AcrobatReaderCtlKiller.
[sourcecode language="csharp"]
using System;
using System.Diagnostics;
namespace AcrobatReaderCtlKiller {
class Program {
static void Main(string[] args) {
try {
Process proc = Process.GetProcessById(Int32.Parse(args[0]));
proc.Kill();
}
catch (Exception) { }
}
}
}
[/sourcecode]
This executable takes just one argument – process ID. Then it finds Acrobat Reader by process ID and closes it, simple as that. Using PID we could start multiple instances of Acrobat Reader without worrying of killing incorrect one.
Most important part of this solution is event gateway itself. First of all print job – it is represented by PrintJob Java class.
[sourcecode language="java"]
public class PrintJob {
private String file;
private String printer;
private String owner;
public PrintJob(String file, String printer, String owner) {
super();
this.file = file;
this.printer = printer;
this.owner = owner;
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
public String getPrinter() {
return printer;
}
public void setPrinter(String printer) {
this.printer = printer;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
[/sourcecode]
And now PrintQueue class – this is ColdFusion event gateway main class. Parts of the code were taken from sample EmptyGateway class coming with ColdFusion installation.
[sourcecode language="java"]
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import coldfusion.eventgateway.CFEvent;
import coldfusion.eventgateway.Gateway;
import coldfusion.eventgateway.GatewayServices;
import coldfusion.eventgateway.Logger;
public class PrintQueue implements Gateway {
// The handle to the CF gateway service
protected GatewayServices gatewayService = null;
// ID provided by EventService
protected String gatewayID = "";
// Listener CFC paths for our events
protected String[] listeners = null;
// Path to my configuration file
protected String config = null;
// The thread that is running the listener
protected Thread listenerThread = null;
// Should we shutdown?
protected boolean shutdown = false;
// timeout for waiting for listener thread to die - 10 seconds
protected static final int TEN_SECONDS = 10 * 1000;
// Out status
protected int status = STOPPED;
// logger
protected Logger log;
// event gateway properties
protected Properties properties = new Properties();
// acrobat reader executable path:
protected String acrobatReaderPath = "";
// AcrobatReaderStartPid exe path:
protected String acrobatStarterPath = "";
// AcrobatReaderCtlKiller exe path:
protected String acrobatReaderKillerPath = "";
// queue holding print jobs when all current print threads are busy:
protected ArrayList queue;
// currently printing queue:
protected ArrayList currentlyInPrint;
// max simultaneous print jobs:
protected int maxPrintJobs = 10;
// when true debug output is added to print-queue log:
protected boolean debug = true;
}
[/sourcecode]
Next step is the class constructor with the loadProperties() method. Here they are:
[sourcecode language="java"]
public PrintQueue(String gatewayID, String config) {
this.gatewayID = gatewayID;
this.config = config;
this.gatewayService = GatewayServices.getGatewayServices();
this.queue = new ArrayList();
this.currentlyInPrint = new ArrayList();
this.log = gatewayService.getLogger("print-queue");
try {
FileInputStream propsFile = new FileInputStream(config);
properties.load(propsFile);
propsFile.close();
this.loadProperties();
}
catch (IOException e) {
log.warn("PrintQueue(" + gatewayID + ") Unable to read configuration file '" + config + "'.", e);
}
}
private void loadProperties() {
this.acrobatReaderKillerPath = properties.getProperty("acrobatReaderKillerPath");
this.acrobatStarterPath = properties.getProperty("acrobatStarterPath");
this.acrobatReaderPath = properties.getProperty("acrobatReaderPath");
this.maxPrintJobs = Integer.parseInt(properties.getProperty("maxPrintJobs"));
this.debug = (Integer.parseInt(properties.getProperty("debug")) == 1);
if (this.debug) {
log.info("PrintQueue(" + gatewayID + ") Loaded with following configuration:");
log.info("PrintQueue(" + gatewayID + ") ------------------------------------");
log.info("PrintQueue(" + gatewayID + ") Acrobat Reader Path: " + this.acrobatReaderPath);
log.info("PrintQueue(" + gatewayID + ") Acrobat Reader Starter: " + this.acrobatStarterPath);
log.info("PrintQueue(" + gatewayID + ") Acrobat Reader killer: " + this.acrobatReaderKillerPath);
log.info("PrintQueue(" + gatewayID + ") ------------------------------------");
}
}
[/sourcecode]
Up to now the code is quite simple. At the beginning we have class definition with all properties used inside and all required imports. PrintQueue constructor is a standard event gateway constructor. It takes gateway instance name declared in CF Administrator and path to .properties file. Properties are loaded by loadProperties() method. Here is a sample .properties file that we’re using:
acrobatReaderKillerPath=c:/Inetpub/AcrobatReaderCtlKiller.exe acrobatStarterPath=c:/Inetpub/AcrobatReaderCtlStarter.exe acrobatReaderPath=C:/Program Files/Adobe/Reader 9.0/Reader/AcroRd32.exe # This is in fact number of Acrobat Reader instances running at the same time: maxPrintJobs=5 debug=0
To fulfil interface requirements bunch of standard event gateway methods are required. They don’t require special comments:
[sourcecode language="java"]
public void setCFCListeners(String[] listeners) {
this.listeners = listeners;
}
public coldfusion.eventgateway.GatewayHelper getHelper() {
// We have no helper class to provide to the CFML programmer
return null;
}
public void setGatewayID(String id) {
gatewayID = id;
}
public String getGatewayID() {
return gatewayID;
}
public void start() {
status = STARTING;
// Start up listener thread
Runnable r = new Runnable() {
public void run() {
listener();
}
};
listenerThread = new Thread(r);
shutdown = false;
listenerThread.start();
status = RUNNING;
}
public void stop() {
status = STOPPING;
// tell generator to stop
shutdown = true;
try {
listenerThread.interrupt();
listenerThread.join(TEN_SECONDS);
}
catch (InterruptedException e) {
// ignore
}
status = STOPPED;
}
public void restart() {
stop();
start();
}
public int getStatus() {
return status;
}
[/sourcecode]
It is time to handle incoming messages. The outgoingMessage message is called when sendGatewayMessage method is executed from CFC component (BTW: if anyone knows why method for handling incoming messages is called outgoingMessage I would appreciate short explanation). This method expects 3 arguments sent from the CFC:
- full path to PDF file
- printer name
- owner name, string representing ID of the user who created print job or full name
When executed it first checks how many instances of Acrobat Reader are running at the moment. If maxPrintJobs is not reached print job is sent directly to Acrobat Reader, otherwise print job is queued for later print. Here is the code:
[sourcecode language="java"]
public String outgoingMessage(coldfusion.eventgateway.CFEvent cfmsg) {
Map data = cfmsg.getData();
// we have two params here:
String file = data.get("FILE").toString();
String printer = data.get("PRINTER").toString();
String owner = data.get("OWNER").toString();
if (this.debug)
log.info("PrintQueue(" + gatewayID + ") Print job received: " + printer + " <- " + file);
synchronized (this.currentlyInPrint) {
// if we have more than N print jobs running
// we need to save print job for later:
if ( this.currentlyInPrint.size() >= this.maxPrintJobs ) {
synchronized (this.queue) {
if (this.debug)
log.info("PrintQueue(" + gatewayID + ") Sending print job to queue.");
PrintJob job = new PrintJob(file, printer, owner);
this.sendCFCMessage(job, "queuing", "add");
this.queue.add( job );
}
} else {
if (this.debug)
log.info("PrintQueue(" + gatewayID + ") Sending print job directly to Acrobat Reader.");
this.executePrint(new PrintJob(file,printer,owner));
}
}
return "OK";
}
[/sourcecode]
Next discussed method is listener() method. This one is executed as Runnable from start() method. It is the thread observing the print queue. It decides when to send the print job to Acrobat Reader if there are any jobs waiting in print queue.
[sourcecode language="java"]
protected void listener() {
while (!shutdown) {
// check if there are jobs in queue:
PrintJob job = null;
synchronized (this.queue) {
if ( this.queue.size() > 0 ) {
synchronized (this.currentlyInPrint) {
if ( this.currentlyInPrint.size() < this.maxPrintJobs ) {
if (this.debug)
log.info("PrintQueue(" + gatewayID + ") Queued print job found. Moving to print ("+this.currentlyInPrint.size()+").");
job = (PrintJob)this.queue.get(0);
this.queue.remove(job);
}
}
}
}
if ( job != null ) {
this.executePrint(job);
}
// sleep here?
try { Thread.sleep(100); } catch (InterruptedException ex) { /* ignore */ }
}
}
[/sourcecode]
Two last PrintQueue methods are executePrint() and sendCFCMessage(). Here they are:
[sourcecode language="java"]
private void executePrint(PrintJob job) {
PrintWorker worker = new PrintWorker(job);
worker.start();
}
private void sendCFCMessage(PrintJob job, String status, String action) {
CFEvent event = new CFEvent(gatewayID);
event.setCfcMethod("onPrintStatus");
Hashtable mydata = new Hashtable();
mydata.put("FILE", job.getFile().substring(job.getFile().lastIndexOf('/')+1));
mydata.put("PRINTER", job.getPrinter());
mydata.put("STATUS", status);
mydata.put("ACTION", action);
mydata.put("OWNER", job.getOwner());
event.setData(mydata);
event.setGatewayType("PrintQueue");
event.setOriginatorID("");
for (int i=0; i<listeners.length; i++) {
// Set CFC path
event.setCfcPath(listeners[i]);
// send it to the event service
gatewayService.addEvent(event);
}
}
[/sourcecode]
The sendCFCMessage() method sends messages back to CFC listeners. This allowed us to provide real-time print queue feedback for Flex application users. File name does not tell them probably what exactly is going on but because we are passing their names back and forth they at least have some idea where their job is in the queue.
Last part of the event gateway is internal class called PrintWorker. It extends java.lang.Thread and is used to execute Acrobat Reader instance and send document to the printer.
[sourcecode language="java"]
class PrintWorker extends Thread {
private PrintJob job;
public PrintWorker(PrintJob job) {
this.job = job;
}
public void run() {
synchronized (currentlyInPrint) {
if (debug)
log.info("PrintQueue(" + gatewayID + ") Adding job to CURRENTLY PRINTING jobs queue.");
currentlyInPrint.add(job);
}
sendCFCMessage(job, "sending to printer", "void");
try {
// execute Acrobat Reader command:
Process p = Runtime.getRuntime().exec(
acrobatStarterPath+" \"" + acrobatReaderPath + "\" \"" + job.getFile() + "\" \"" + job.getPrinter() + "\"");
BufferedReader rd = new BufferedReader(new InputStreamReader(p.getInputStream()));
// just one line - process id:
String pid = rd.readLine().trim();
if (debug)
log.info("PrintQueue(" + gatewayID + ") Acrobat Reader started with process id " + pid + ". Sleeping...");
// sleep - let AR send document to the printer:
try { Thread.sleep(5000); } catch (InterruptedException ex) { /* ignore */ }
// execute Acrobat Killer
if (debug)
log.info("PrintQueue(" + gatewayID + ") Document sent to the printer. Killing Acrobat Reader " + pid + ". Cleaning.");
Runtime.getRuntime().exec(acrobatReaderKillerPath + " " + pid);
// cleaning up:
File pdfFile = new File(job.getFile());
pdfFile.delete();
}
catch (IOException ex) {
if (debug)
log.warn("PrintQueue(" + gatewayID + ") Can't print job.", ex);
}
synchronized (currentlyInPrint) {
if (debug)
log.info("PrintQueue(" + gatewayID + ") Removing job from CURRENTLY PRINTING jobs queue.");
currentlyInPrint.remove(job);
}
sendCFCMessage(job, "printed", "remove");
}
}
[/sourcecode]
And that is all the code for the PrintQueue event gateway. To install event gateway successfully a CFC file is required. PrintQueue.cfc is the file which handles all messages coming from event gateway. It is also used as RemoteObject by Flex. As you can see in Java source code CFC messages are sent in three different situations:
- when print job is queued (
outgoingMessage) - when print job is sent to the printer, Acrobat Reader starting (listener)
- when print job was sent by Acrobat Reader to the printer (possibly printer is starting printing at this point)
Below is the CFC that handles printer queue at ColdFusion side:
[sourcecode language="cf"]
<cfcomponent>
<cffunction name="onPrintStatus" output="no">
<cfargument name="CFEvent" type="struct" required="yes" />
<!--- Get the message --->
<cfset var data=cfevent.DATA />
<!---
There are two properties:
- data.FILE
- data.PRINTER
- data.STATUS
- data.ACTION
--->
<cfswitch expression="#data.action#">
<cfcase value="add">
<cfif not structKeyExists(application, "printQueue")>
<cfset application.printQueue = arrayNew(1) />
</cfif>
<cfset arrayAppend(application.printQueue, data) />
</cfcase>
<cfcase value="remove">
<cfscript>
tArr = arrayNew(1);
for (i=1; i lte arrayLen(application.printQueue); i=i+1)
if ( application.printQueue[i].file neq data.file )
arrayAppend(tArr, application.printQueue[i]);
application.printQueue = tArr;
</cfscript>
</cfcase>
<cfdefaultcase>
<cfscript>
for (i=1; i lte arrayLen(application.printQueue); i=i+1)
if ( application.printQueue[i].file neq data.file ) {
application.printQueue[i].status = data.status;
break;
}
</cfscript>
</cfdefaultcase>
</cfswitch>
</cffunction>
<cffunction name="getPrintQueue" access="remote" output="false" returntype="array">
<cfif not structKeyExists(application, "printQueue")>
<cfset application.printQueue = arrayNew(1) />
</cfif>
<cfreturn application.printQueue />
</cffunction>
</cfcomponent>
[/sourcecode]
The onPrintStatus() method manages printQueue stored in application scope. Second method is used by Flex to pull print queue every N millisecond. Lastly lets see how messages are sent to event gateway:
[sourcecode language="cf"]
<cffunction name="print" output="false" returntype="void" access="remote">
<cfargument name="file" type="string" />
<cfargument name="printerName" type="string" />
<cfargument name="owner" type="string" />
<cfscript>
props = structNew();
props.method="outgoingMessage";
props.FILE = arguments.file;
props.PRINTER = arguments.printerName;
props.OWNER = arguments.owner;
status = SendGatewayMessage("PrintQueue-#CGI.SERVER_NAME#", props);
</cfscript>
</cffunction>
[/sourcecode]
And that is all.
Full source code of C# applications, event gateway and PrintQueue.cfc can be downloaded from here.
All source code is available under MIT license.
Running the code
Event gateways can be executed using ColdFusion Enterprise only. Event gateway was created with Java 1.4.2 so it should run fine with ColdFusion 7. To run the code export event gateway as JAR file and drop it to {CFUSION_INSTALL_DIR}/lib directory and restart ColdFusion. Next go to ColdFusion Administrator, Event Gateways section. Create new gateway type:
- Name: PrintQueue
- Description: whatever description you want
- Java class: uk.co.monochrome.print.queue.PrintQueue
Next go to Gateway Instances page and create new gateway instance:
- Gateway ID: PrintQueue-YOUR.HOST
- Gateway type: select newly created type
- CFC path: type full PrintQueue.cfc path
- Configuration file: type full .properties file path
Don’t forget to deploy exe files and change paths in .properties file. Once it is done you can start your gateway.
Posted by Neil Middleton on 10 Nov 2008
First CF Doc's podcast now live!
The first episode of the UKCFUG’s CF Doc podcast is now live!
Monochrome’s Niklas Richardson and Neil Middleton, along with CFEclipse fame Mark Drew kicked off the first episode of the CF Doc podcast in front of a live audience at last week’s November UKCFUG meeting.
You can read more about the first episode on the UKCFUG website.
Posted by Niklas Richardson on 10 Nov 2008
Why player penetration isn't as important as you might think
Something we come across a lot when we’re talking about Rich Internet Applications is the questions regarding player penetration, i.e how many people have player X installed (Adobe’s Flash vs. Microsoft’s Silverlight), as it’s generally seen as the primary measure for how easy to “see” an application will be.
However, something we’re also seeing is that the players are currently suited to different areas, Flash being very good for the public sites (video being No 1) and Silverlight being very good for Intranet based applications where Microsoft technology is used as the back end.
So, with player penetration it’s worth considering the following: Is the player penetration at all important when you are looking at an intranet application with a closed user audience? Do you need to worry about the percentage of the internet that has your runtime installed if you can go round and install it on all your users machines for them anyway?
Not really.
This raises another interesting thing, which is that of internal approval. We are now finding that for those environments where neither Flash (in a new enough version) or Silverlight are present, companies are generally more willing to take on Silverlight due to the vendor – it’s a product that comes from a company they already have a relationship with, and also one that can be pushed out via Windows Update and the like. It’s a sys-admins dream come true.
So, at the end of the day it seems that Flash vs Silverlight is definitely a case of best tool for the job. If you are building a public facing site of some kind, use the Flash platform. However, if you’re in a closed environment and MS are already in place – go with Silverlight / WPF. These choices will just make your life easier.
PS: Incidentally, we still come across lots of businesses who haven’t broken free of IE6, a seven year old browser yet (!) due to some internal policy – this goes to show how hard it can sometimes be bringing in a third party plugin to help with RIA’s.
Posted by Neil Middleton on 12 Nov 2008
Videos from Adobe MAX 2008
Adobe have just posted up a load of videos taken at this years MAX conference in San Francisco via their Adobe Developer Connection blog.
The videos are:
MAX 2008: Adobe kicks off Day 1 in San Francisco keynote highlights
MAX 2008: From Adobe Flash Catalyst to Adobe CS4, Day 2
What was your geek-out moment of the day?
Interviews/demo with MAX Award Winners
We’re pretty psyched about the stuff coming out of Adobe at the moment, so we’re finding it really hard to wait for MAX in Milan in a couple of weeks! See you there!
Posted by Neil Middleton on 21 Nov 2008
VAT rate changes on Dec 1st – Only 5 days!
Unless youve been living under a rock youll be aware that the HMRC has temporarily reduced the rate from 17.5% with effect from December 1st to 15%. With the current rate having been in place since 1991 companies may find that their legacy applications have had the rate hard coded where developers have taken shortcuts whilst developing applications. Its important to note that correct application of VAT rates is entirely the down to the business owner to ensure is correct.
The change means that websites need to have their VAT rate calculations updated to reflect the new value on sales from Dec 1st but remember that it is a temporary change so will need to be changed back or a mechanism put in place to do it for you.
Unfortunately this change comes into place in under 5 working days which doesnt leave you much time to update/test existing applications. If you find yourself in a position that youre unable to update your applications in Flex or ColdFusion VAT rate then Monochrome can help so get in touch.
In this current climate of change and uncertainty your website can be your most important asset in increasing your exposure and revenue stream. Monochrome can assist in the design, development and deployment of rich user experiences that have a significant impact to your online presence. Let Monochrome bring your site to life!
Posted by on 25 Nov 2008
Where you can find us at MAX Europe
Both myself and Niklas will be attending (and speaking at) this years MAX conference in Milan next week and are mightily looking forward to it. In case you’re wanting to come and hook up with us, here’s our agenda for the three days.
If you’re in any of the sessions we’re in, please pop over and say “hello”!
| December 1, 2008 | |
| Object-Relational Mapping in ColdFusion 9 | 11:30 – 12:30 |
| Adobe Roadmap: Rich Internet Applications | 14:00 – 15:00 |
| Adobe XD: Designing Design | 15:15 – 16:15 |
| Adobe Roadmap: Web Experiences | 16:30 – 17:30 |
December 2, 2008 |
|
| Adobe@Adobe: IT Innovation It’s Not an Oxymoron | 09:00 – 10:00 |
| Architecting ColdFusion for Scalability and High Availability | 13:30 – 14:30 |
| Flex Development with Cairngorm | 14:45 – 15:45 |
| Wireframing Experiences and Applications | 16:00 – 17:00 |
December 3, 2008 |
|
| Breathe AIR into Your Brand | 09:00 – 10:00 |
| Building Enterprise Applications Using Flex, Adobe AIR, and LiveCycle ES | 10:15 – 11:15 |
| Looking Ahead to the Next Version of Flex | 11:30 – 12:30 |
| Building High-Performance Applications for Adobe AIR | 13:30 – 14:30 |
| Structuring Your Development Team to Get the Work Done | 14:45 – 15:45 |
| Flex and PHP: Working with Flex Builder and Zend Studio for Eclipse | 16:00 – 17:00 |
Posted by Neil Middleton on 26 Nov 2008
| www.flickr.com |
Archives
Posted by Neil Middleton on 01 Jan 1910
From our portfolio
| www.flickr.com |
