/**
* LpdLinePrinter Class
*
* Printer driver implementation to communicate to network printer using LDP protocol.
* See: http://lprng.sourceforge.net/LPRng-Reference-Multipart/x4882.htm
*
* LPD Protocol have severe security and usability limitation. Among other things:
* - No error messages or status capability
* - Limited or very primitive banner printing. On some systems it may be impossible
* to turn banner printing off.
* - On most known print servers high connection activity caused by multiple systems
* attempting to get status or spool jobs may cause catastrophic failure of the printer.
* - Connection from client must use priviledged port (instead of out of bound port).
* This would require client to have root setuid or to run as root.
* - The default range for client port are very limited. Rapid sending of printing job
* would be stucked after the 12th request.
*
* For the above reasons, using RFC1179 to transfer jobs to a printer should be regarded
* as the least desirable option.
*
* Original implementation is nicked from internet. Unfortunately I don't remember where
* I got it from.
*
* @author Wahyu Yoga Pratama (wyogap@thatcoolguy.com)
*
* @created Jan 29, 2010
* @version $$
*
* HISTORY:
* - 2010/01/29 Created.
*
*/
package tcg.print;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class LpdLinePrinter implements ILinePrinter
{
private static final int MAX_RETRIES = 30;
private static final int DEF_SOCK_TIMEOUT_MILLIS = 500;
private static final int LPD_LISTEN_PORT = 515;
private static final int LPD_MIN_CLIENT_PORT = 721;
private static final int LPD_MAX_CLIENT_PORT = 731;
private String hostname_ = "";
private String queuename_ = "";
private String documentName_ = "";
private int timeoutMillis_ = DEF_SOCK_TIMEOUT_MILLIS;
private boolean useOutOfBoundsPorts_ = false;
private int jobNumber_ = 0;
private boolean printRaw_ = true;
public LpdLinePrinter(String host, String queue)
{
if (host != null) hostname_ = host;
if (queue != null) queuename_ = queue;
//use hostname as document name
try
{
documentName_ = InetAddress.getLocalHost().getHostName();
}
catch(UnknownHostException he)
{
//ignore
}
}
/**
* By default we prints all printing data as raw binary. If you need to use
* the text formatting of the spooler on your host set this value to false
*
* @param flag - whether we should print data as raw binary
*/
public void setPrintRaw(boolean flag)
{
printRaw_ = flag;
}
/**
* @see #setPrintRaw
*/
public boolean getPrintRaw()
{
return(printRaw_);
}
/**
* The RFC for lpr specifies the use of local ports numbered 721 - 731, however
* TCP/IP also requires that any port that is used will not be released for 3 minutes
* which means that rapid printing will get stuck on the 12th job.
*
* To resolve this issue you can use out of bounds ports which most print servers
* will support
*
* @param flag - whether we should use out of bound port to connect to LPD server
*/
public void setUseOutOfBoundPorts(boolean flag)
{
useOutOfBoundsPorts_ = flag;
}
/**
* @see #setUseOutOfBoundPorts
*/
public boolean getUseOutOfBoundPorts()
{
return(useOutOfBoundsPorts_);
}
/**
* Ping the remote LPD server.
*
* @return true if we can connect to remote LPD server, false otherwise
*/
public boolean ping()
{
//open connection to the print server
Socket sock = new Socket();
try
{
sock.bind(null);
sock.connect(new InetSocketAddress(hostname_, LPD_LISTEN_PORT), timeoutMillis_);
}
catch (IOException ioe)
{
return false;
}
//open dummy input stream and output stream
try
{
sock.getOutputStream();
sock.getInputStream();
}
catch (IOException ioe)
{
return false;
}
//close the connection
try
{
sock.close();
}
catch(IOException ioe)
{
//ignore
}
//successful
return true;
}
/**
* Print a printable object
*
* @param printable - the printable object
* @return true if we can print successfully, false otherwise
* @throws RuntimeException if there is an error
*/
public boolean print(IPrintable printable) throws RuntimeException
{
//validation
if (printable == null) return true;
String controlFile = "";
byte buffer[] = new byte[1000];
String str;
String strJobNumber;
//generate job number. Job number cycles from 001 to 999
if (++jobNumber_ >= 1000)
{
jobNumber_ = 1;
}
strJobNumber = "" + jobNumber_;
while (strJobNumber.length() < 3)
{
strJobNumber = "0" + strJobNumber;
}
//get user name
String username = System.getProperty("user.name");
if (username == null)
{
username = "Unknown";
}
//try to open socket connection
Socket sock = getSocket();
//open input stream and output stream
OutputStream os = null;
InputStream is = null;
try
{
os = sock.getOutputStream();
is = sock.getInputStream();
}
catch (IOException ioe)
{
throw new RuntimeException("print: can not get data stream from remote LPD server "
+ hostname_ + ". Error: " + ioe.getMessage());
}
//open printer
try
{
str = "\002" + queuename_ + "\n";
os.write(str.getBytes());
os.flush();
}
catch (IOException ioe)
{
throw new RuntimeException("print: failed to open printer " + queuename_
+ ". Error: " + ioe.getMessage());
}
acknowledge(is, "print: failed to open printer " + queuename_);
//build control file
controlFile += "H" + hostname_ + "\n";
controlFile += "P" + username + "\n";
controlFile += ((printRaw_) ? "o":"p") +"dfA" + strJobNumber + hostname_ + "\n";
controlFile += "UdfA" + strJobNumber + hostname_ + "\n";
controlFile += "N" + documentName_ + "\n";
//send the length of the control file
try
{
str = "\002" + (controlFile.length()) + " cfA" + strJobNumber + hostname_ + "\n";
os.write(str.getBytes());
os.flush();
}
catch (IOException ioe)
{
throw new RuntimeException("print: failed to send control header. Error: "
+ ioe.getMessage());
}
acknowledge(is, "print: failed to send control header");
//send the control file
try
{
os.write(controlFile.getBytes());
buffer[0] = 0;
os.write(buffer, 0, 1);
os.flush();
}
catch (IOException ioe)
{
throw new RuntimeException("print: failed to send control file. Error: "
+ ioe.getMessage());
}
acknowledge(is, "print: failed to send control file");
//get the bytes to send to print server
byte[] data = printable.getBytes();
//send the data length
try
{
str = "\003" + (data.length) + " dfA" + strJobNumber + hostname_ + "\n";
os.write(str.getBytes());
os.flush();
}
catch (IOException ioe)
{
throw new RuntimeException("print: failed to send data header. Error: "
+ ioe.getMessage());
}
acknowledge(is, "print: failed to send data header");
//send the data
try
{
os.write(data, 0, data.length);
buffer[0] = 0;
os.write(buffer,0,1);
os.flush();
}
catch (IOException ioe)
{
throw new RuntimeException("print: failed to send printing data. Error: "
+ ioe.getMessage());
}
acknowledge(is, "print: failed to send printing data");
try
{
sock.close();
}
catch (IOException ioe)
{
//ignore
}
return true;
}
/**
* Print a printable object.
*
* This is a hack function allowing us to call print() without worrying to catch
* the possible runtime exception, thus simplifying the code. This should only be used
* when we do not care what the possible failure reason is.
*
* @param printable - the printable object
* @return true if we can print successfully, false otherwise
*/
public boolean _print(IPrintable printable)
{
try
{
return print(printable);
}
catch (RuntimeException re)
{
//ignore
return false;
}
}
private Socket getSocket() throws RuntimeException
{
/*
* The LPD Protocol only allows connection from client port 721 to 731 inclusive.
* However TCP/IP also requires that any port that is used will not be released
* for 3 minutes which means rapid printing will be stucked on 12th job.
*
* The workaround is to ignore this requirement and use out of bound port.
* (Although this does not comply with the protocol, most LPD server will support it).
*/
Socket sock = null;
if (useOutOfBoundsPorts_)
{
try
{
sock = new Socket(hostname_, LPD_LISTEN_PORT);
sock.setSoTimeout(timeoutMillis_);
}
catch (IOException ioe)
{
throw new RuntimeException("getSocket: can not connect to remote LPD server "
+ hostname_ + ". Error: " + ioe.getMessage());
}
}
else
{
for(int j = 0; (j < MAX_RETRIES) && (sock == null); j++)
{
for (int i = LPD_MIN_CLIENT_PORT;
(i <= LPD_MAX_CLIENT_PORT) && (sock == null); i++)
{
try
{
sock = new Socket(hostname_, LPD_LISTEN_PORT,
InetAddress.getLocalHost(), i);
sock.setSoTimeout(timeoutMillis_);
}
catch (IOException ioe)
{
//ignore
}
}
//keep trying until we get one
if (sock == null)
{
try
{
Thread.sleep(10000);
}
catch(InterruptedException ie)
{
//ignore
}
}
} //for(int j = 0; (j < MAX_RETRIES) && (sock == null); j++)
}
if (sock == null)
{
throw new RuntimeException("getSocket: can not connect to remote LPD server "
+ hostname_ + ".");
}
return sock;
}
private void acknowledge(InputStream is, String alert) throws RuntimeException
{
try
{
if (is.read() != 0)
{
throw new RuntimeException(alert);
}
}
catch(IOException ioe)
{
throw new RuntimeException(alert + ". Error: " + ioe.getMessage());
}
}
}