
it.could.util.http.WebDavClient Maven / Gradle / Ivy
Show all versions of webdav Show documentation
/* ========================================================================== *
* Copyright (C) 2004-2006, Pier Fumagalli *
* All rights reserved. *
* ========================================================================== *
* *
* Licensed under the Apache License, Version 2.0 (the "License"). You may *
* not use this file except in compliance with the License. You may obtain a *
* copy of the License at . *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT *
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the *
* License for the specific language governing permissions and limitations *
* under the License. *
* *
* ========================================================================== */
package it.could.util.http;
import it.could.util.StreamTools;
import it.could.util.StringTools;
import it.could.util.location.Location;
import it.could.util.location.Path;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* A class implementing an extremely simple WebDAV Level 1 client based on
* the {@link HttpClient}.
*
* Once opened this class will represent a WebDAV collection. Users of this
* class can then from an instance of this, deal with relative parent and
* children resources.
*
* @author Pier Fumagalli
*/
public class WebDavClient {
/** The WebDAV resource asociated with this instance.
*/
private Resource resource;
/** A map of children resources of this instance.
*/
private Map children;
/**
* Create a new {@link WebDavClient} instance opening the collection
* identified by the specified {@link Location}.
*
* @param location the {@link Location} of the WebDAV collection to open.
* @throws IOException if an I/O or network error occurred, or if the
* {@link Location} specified does not point to a
* WebDAV collection.
* @throws NullPointerException if the {@link Location} was null.
*/
public WebDavClient(Location location)
throws NullPointerException, IOException {
if (location == null) throw new NullPointerException("Null location");
this.reload(location);
}
/* ====================================================================== */
/* ACTIONS */
/* ====================================================================== */
/**
* Refresh this {@link WebDavClient} instance re-connecting to the remote
* collection and re-reading its properties.
*
* @return this {@link WebDavClient} instance.
*/
public WebDavClient refresh()
throws IOException {
this.reload(this.resource.location);
return this;
}
/**
* Fetch the contents of the specified child resource of the collection
* represented by this {@link WebDavClient} instance.
*
* @see #isCollection(String)
* @return a non-null {@link InputStream}.
* @throws IOException if an I/O or network error occurred, or if the
* child specified represents a collection.
* @throws NullPointerException if the child was null.
*/
public InputStream get(String child)
throws NullPointerException, IOException {
if (child == null) throw new NullPointerException("Null child");
if (! this.isCollection(child)) {
final Location location = this.getLocation(child);
final HttpClient client = new HttpClient(location);
client.setAcceptableStatus(200).connect("GET");
return client.getResponseStream();
}
throw new IOException("Child \"" + child + "\" is a collection");
}
/**
* Delete the child resource (or collection) of the collection
* represented by this {@link WebDavClient} instance.
*
* @return this {@link WebDavClient} instance.
* @throws IOException if an I/O or network error occurred, or if the
* child specified represents a collection.
* @throws NullPointerException if the child was null.
*/
public WebDavClient delete(String child)
throws NullPointerException, IOException {
if (child == null) throw new NullPointerException("Null child");
final HttpClient client = new HttpClient(this.getLocation(child));
client.setAcceptableStatus(204).connect("DELETE").disconnect();
return this.refresh();
}
/**
* Create a new collection as a child of the collection represented
* by this {@link WebDavClient} instance.
*
* In comparison to {@link #put(String)} and {@link #put(String, long)}
* this method will fail if the specified child already exist.
*
* @see #hasChild(String)
* @return this {@link WebDavClient} instance.
* @throws IOException if an I/O or network error occurred, or if the
* child specified already exist.
* @throws NullPointerException if the child was null.
*/
public WebDavClient mkcol(String child)
throws NullPointerException, IOException {
if (child == null) throw new NullPointerException("Null child");
if (this.hasChild(child))
throw new IOException("Child \"" + child + "\" already exists");
final Location location = this.resource.location.resolve(child);
final HttpClient client = new HttpClient(location);
client.setAcceptableStatus(201).connect("MKCOL").disconnect();
return this.refresh();
}
/**
* Create a new (or update the contents of a) child of of the collection
* represented by this {@link WebDavClient} instance.
*
* This method will behave exactly like the {@link #put(String, long)}
* method, but the data written to the returned {@link OutputStream} will
* be buffered in memory and will be transmitted to the remote
* server only when the {@link OutputStream#close()} method is called.
*
* If the returned {@link OutputStream} is garbage collected before the
* {@link OutputStream#close() close()} method is called, the entire
* transaction will be aborted and no connection to the remote server will
* be established.
*
* Use this method in extreme cases. In normal circumstances always rely
* on the {@link #put(String, long)} method.
*
* @see #put(String, long)
* @return a non-null {@link OutputStream} instance.
* @throws NullPointerException if the child was null.
*/
public OutputStream put(final String child)
throws NullPointerException {
if (child == null) throw new NullPointerException("Null child");
final WebDavClient client = this;
return new ByteArrayOutputStream() {
private boolean closed = false;
public void close()
throws IOException {
if (this.closed) return;
this.flush();
OutputStream output = client.put(child, this.buf.length);
output.write(this.buf);
output.flush();
output.close();
}
protected void finalize()
throws Throwable {
this.closed = true;
super.finalize();
}
};
}
/**
* Create a new (or update the contents of a) child of of the collection
* represented by this {@link WebDavClient} instance.
*
* If the specified child {@link #hasChild(String) already exists} on
* the remote server, it will be {@link #delete(String) deleted} before
* writing.
*
* @return a non-null {@link OutputStream} instance.
* @throws NullPointerException if the child was null.
* @throws IOException if an I/O or network error occurred, or if the
* child specified already exist.
*/
public OutputStream put(String child, long length)
throws NullPointerException, IOException {
if (child == null) throw new NullPointerException("Null child");
if (this.hasChild(child)) this.delete(child);
final Location location = this.resource.location.resolve(child);
final HttpClient client = new HttpClient(location);
client.setAcceptableStatuses(new int[] { 201, 204 });
client.connect("PUT", length);
final WebDavClient webdav = this;
return new BufferedOutputStream(client.getRequestStream()) {
boolean closed = false;
public void close()
throws IOException {
if (this.closed) return;
try {
super.close();
} finally {
this.closed = true;
webdav.refresh();
}
}
protected void finalize()
throws Throwable {
try {
this.close();
} finally {
super.finalize();
}
}
};
}
/**
* Open the specified child collection of the collection represented by
* this {@link WebDavClient} as a new {@link WebDavClient} instance.
*
* If the specified child is ".
" this method
* will behave exactly like {@link #refresh()} and this instance
* will be returned.
*
* If the specified child is "..
" this method
* will behave exactly like {@link #parent()}.
*
* @return a non-null {@link WebDavClient} instance.
* @throws NullPointerException if the child was null.
* @throws IOException if an I/O or network error occurred, or if the
* child specified did not exist.
*/
public WebDavClient open(String child)
throws NullPointerException, IOException {
if (child == null) throw new NullPointerException("Null child");
if (".".equals(child)) return this.refresh();
if ("..".equals(child)) return this.parent();
if (resource.collection) {
Location loc = this.getLocation().resolve(this.getLocation(child));
return new WebDavClient(loc);
}
throw new IOException("Child \"" + child + "\" is not a collection");
}
/**
* Open the parent collection of the collection represented by this
* {@link WebDavClient} as a new {@link WebDavClient} instance.
*
* @return a non-null {@link WebDavClient} instance.
* @throws IOException if an I/O or network error occurred, or if the
* child specified did not exist.
*/
public WebDavClient parent()
throws IOException {
final Location location = this.resource.location.resolve("..");
return new WebDavClient(location);
}
/* ====================================================================== */
/* ACCESSOR METHODS */
/* ====================================================================== */
/**
* Return an {@link Iterator} over {@link String}s for all the children
* of the collection represented by this {@link WebDavClient} instance.
*/
public Iterator iterator() {
return this.children.keySet().iterator();
}
/**
* Checks if the collection represented by this {@link WebDavClient}
* contains the specified child.
*/
public boolean hasChild(String child) {
return this.children.containsKey(child);
}
/**
* Return the {@link Location} associated with the collection
* represented by this {@link WebDavClient}.
*
* The returned {@link Location} can be different from the one specified
* at construction, in case the server redirected us upon connection.
*/
public Location getLocation() {
return this.resource.location;
}
/**
* Return the content length (in bytes) of the collection represented
* by this {@link WebDavClient} as passed to us by the WebDAV server.
*/
public long getContentLength() {
return this.resource.contentLength;
}
/**
* Return the content type (mime-type) of the collection represented
* by this {@link WebDavClient} as passed to us by the WebDAV server.
*/
public String getContentType() {
return this.resource.contentType;
}
/**
* Return the last modified {@link Date} of the collection represented
* by this {@link WebDavClient} as passed to us by the WebDAV server.
*/
public Date getLastModified() {
return this.resource.lastModified;
}
/**
* Return the creation {@link Date} of the collection represented
* by this {@link WebDavClient} as passed to us by the WebDAV server.
*/
public Date getCreationDate() {
return this.resource.creationDate;
}
/**
* Return the {@link Location} associated with the specified child of
* the collection represented by this {@link WebDavClient}.
*
* @throws IOException if the specified child does not exist.
* @throws NullPointerException if the specified child was null.
*/
public Location getLocation(String child)
throws IOException {
Location location = this.getResource(child).location;
return this.resource.location.resolve(location);
}
/**
* Checks if the specified child of the collection represented by this
* {@link WebDavClient} instance is a collection.
*/
public boolean isCollection(String child)
throws IOException {
return this.getResource(child).collection;
}
/**
* Return the content length (in bytes) associated with the specified
* child of the collection represented by this {@link WebDavClient}.
*
* @throws IOException if the specified child does not exist.
* @throws NullPointerException if the specified child was null.
*/
public long getContentLength(String child)
throws IOException {
return this.getResource(child).contentLength;
}
/**
* Return the content type (mime-type) associated with the specified
* child of the collection represented by this {@link WebDavClient}.
*
* @throws IOException if the specified child does not exist.
* @throws NullPointerException if the specified child was null.
*/
public String getContentType(String child)
throws IOException {
return this.getResource(child).contentType;
}
/**
* Return the last modified {@link Date} associated with the specified
* child of the collection represented by this {@link WebDavClient}.
*
* @throws IOException if the specified child does not exist.
* @throws NullPointerException if the specified child was null.
*/
public Date getLastModified(String child)
throws IOException {
return this.getResource(child).lastModified;
}
/**
* Return the creation {@link Date} associated with the specified
* child of the collection represented by this {@link WebDavClient}.
*
* @throws IOException if the specified child does not exist.
* @throws NullPointerException if the specified child was null.
*/
public Date getCreationDate(String child)
throws IOException {
return this.getResource(child).creationDate;
}
/* ====================================================================== */
/* INTERNAL METHODS */
/* ====================================================================== */
/**
* Return the resource associated with the specified child.
*
* @throws IOException if the specified child does not exist.
* @throws NullPointerException if the specified child was null.
*/
private Resource getResource(String child)
throws IOException {
if (child == null) throw new NullPointerException();
final Resource resource = (Resource) this.children.get(child);
if (resource == null) throw new IOException("Not found: " + child);
return resource;
}
/**
* Contact the remote WebDAV server and fetch all properties.
*/
private void reload(Location location)
throws IOException {
/* Do an OPTIONS over onto the location */
location = this.options(location);
/* Do a PROPFIND to figure out the properties and the children */
final Iterator iterator = this.propfind(location).iterator();
final Map children = new HashMap();
while (iterator.hasNext()) {
final Resource resource = (Resource) iterator.next();
final Path path = resource.location.getPath();
if (path.size() == 0) {
resource.location = location.resolve(resource.location);
this.resource = resource;
} else if (path.size() == 1) {
final Path.Element element = (Path.Element) path.get(0);
if ("..".equals(element.getName())) continue;
children.put(element.toString(), resource);
}
}
/* Check if the current resource was discovered */
if (this.resource == null)
throw new IOException("Current resource not returned in PROOPFIND");
/* Don't actually allow resources to be modified */
this.children = Collections.unmodifiableMap(children);
}
/**
* Contact the remote WebDAV server and do an OPTIONS lookup.
*/
private Location options(Location location)
throws IOException {
/* Create the new HttpClient instance associated with the location */
final HttpClient client = new HttpClient(location);
client.setAcceptableStatus(200).connect("OPTIONS", true).disconnect();
/* Check that the remote server returned the "Dav" header */
final List davHeader = client.getResponseHeaderValues("dav");
if (davHeader == null) {
throw new IOException("Server did not respond with a DAV header");
}
/* Check if the OPTIONS request contained the DAV header */
final Iterator iterator = davHeader.iterator();
boolean foundLevel1 = false;
while (iterator.hasNext() && (! foundLevel1)) {
String value = (String) iterator.next();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreTokens()) {
if (! "1".equals(tokenizer.nextToken().trim())) continue;
foundLevel1 = true;
break;
}
}
/* Return the (possibly redirected) location or fail miserably */
if (foundLevel1) return client.getLocation();
throw new IOException("Server doesn't support DAV Level 1");
}
/**
* Contact the remote WebDAV server and do a PROPFIND lookup, returning
* a {@link List} of all scavenged resources.
*/
private List propfind(Location location)
throws IOException {
/* Create the new HttpClient instance associated with the location */
final HttpClient client = new HttpClient(location);
client.addRequestHeader("Depth", "1");
client.setAcceptableStatus(207).connect("PROPFIND", true);
/* Get the XML SAX Parser and parse the output of the PROPFIND */
try {
final SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
final SAXParser parser = factory.newSAXParser();
final String systemId = location.toString();
final InputSource source = new InputSource(systemId);
final Handler handler = new Handler(location);
source.setByteStream(client.getResponseStream());
parser.parse(source, handler);
return handler.list;
} catch (ParserConfigurationException exception) {
Exception throwable = new IOException("Error creating XML parser");
throw (IOException) throwable.initCause(exception);
} catch (SAXException exception) {
Exception throwable = new IOException("Error creating XML parser");
throw (IOException) throwable.initCause(exception);
} finally {
client.disconnect();
}
}
/* ====================================================================== */
/* INTERNAL CLASSES */
/* ====================================================================== */
/**
* An internal XML {@link DefaultHandler} used to parse out the various
* details of a PROPFIND response.
*/
private static final class Handler extends DefaultHandler {
/* ================================================================== */
/* PSEUDO-XPATH LOCATIONS FOR QUICK-AND-DIRTY LOCATION LOOKUP */
/* ================================================================== */
private static final String RESPONSE_PATH = "/multistatus/response";
private static final String HREF_PATH = "/multistatus/response/href";
private static final String COLLECTION_PATH =
"/multistatus/response/propstat/prop/resourcetype/collection";
private static final String GETCONTENTTYPE_PATH =
"/multistatus/response/propstat/prop/getcontenttype";
private static final String GETLASTMODIFIED_PATH =
"/multistatus/response/propstat/prop/getlastmodified";
private static final String GETCONTENTLENGTH_PATH =
"/multistatus/response/propstat/prop/getcontentlength";
private static final String CREATIONDATE_PATH =
"/multistatus/response/propstat/prop/creationdate";
/** The {@link Location} for resolving all other links.
*/
private final Location base;
/** The {@link List} of all scavenged resources.
*/
private final List list = new ArrayList();
/** The resource currently being processed.
*/
private Resource rsrc = null;
/** A {@link StringBuffer} holding character data.
*/
private StringBuffer buff = null;
/** A {@link Stack} for quick-and-dirty pseudo XPath lookups.
*/
private Stack stack = new Stack();
/**
* Create a new instance specifying the base {@link Location}.
*/
private Handler(Location location) {
this.base = location;
}
/**
* Push an element name in the stack for pseudo-XPath lookups.
*
* @return a {@link String} like /element/element/element
.
*/
private String pushPath(String path) {
this.stack.push(path.toLowerCase());
final StringBuffer buffer = new StringBuffer();
for (int x = 0; x < this.stack.size(); x ++)
buffer.append('/').append(this.stack.get(x));
return buffer.toString();
}
/**
* Pop the last element name from the pseudo-XPath lookup stack.
*
* @return a {@link String} like /element/element/element
.
*/
private String popPath(String path)
throws SAXException {
final StringBuffer buffer = new StringBuffer();
final String last = (String) this.stack.pop();
if (path.toLowerCase().equals(last)) {
for (int x = 0; x < this.stack.size(); x ++)
buffer.append('/').append(this.stack.get(x));
return buffer.append('/').append(last).toString();
}
throw new SAXException("Tag <" + path + "/> unbalanced at path \""
+ pushPath(last) + "\"");
}
/**
* Handle the start-of-element SAX event.
*/
public void startElement(String uri, String l, String q, Attributes a)
throws SAXException {
if (! "DAV:".equals(uri.toUpperCase())) return;
final String path = this.pushPath(l);
if (RESPONSE_PATH.equals(path)) {
this.rsrc = new Resource();
} else if (COLLECTION_PATH.equals(path)) {
if (this.rsrc != null) this.rsrc.collection = true;
} else if (GETCONTENTTYPE_PATH.equals(path) ||
GETLASTMODIFIED_PATH.equals(path) ||
GETCONTENTLENGTH_PATH.equals(path) ||
CREATIONDATE_PATH.equals(path) ||
HREF_PATH.equals(path)) {
this.buff = new StringBuffer();
}
}
/**
* Handle the end-of-element SAX event.
*/
public void endElement(String uri, String l, String q)
throws SAXException {
if (! "DAV:".equals(uri.toUpperCase())) return;
final String path = this.popPath(l);
final String data = this.resetBuffer();
if (RESPONSE_PATH.equals(path)) {
if (this.rsrc != null) {
if (this.rsrc.location != null) {
if (this.rsrc.location.isAbsolute()) {
final String z = this.rsrc.location.toString();
throw new SAXException("Unresolved location " + z);
} else {
this.list.add(this.rsrc);
}
} else {
throw new SAXException("Null location for resource");
}
}
} else if (HREF_PATH.equals(path)) {
if (this.rsrc != null) try {
final Location resolved = this.base.resolve(data);
this.rsrc.location = this.base.relativize(resolved);
if (! this.rsrc.location.isRelative())
throw new SAXException("Unable to relativize location "
+ this.rsrc.location);
} catch (MalformedURLException exception) {
final String msg = "Unable to resolve URL \"" + data + "\"";
SAXException throwable = new SAXException(msg, exception);
throw (SAXException) throwable.initCause(exception);
}
} else if (CREATIONDATE_PATH.equals(path)) {
if (this.rsrc != null)
this.rsrc.creationDate = StringTools.parseIsoDate(data);
} else if (GETCONTENTTYPE_PATH.equals(path)) {
if (this.rsrc != null) this.rsrc.contentType = data;
} else if (GETLASTMODIFIED_PATH.equals(path)) {
if (this.rsrc != null)
this.rsrc.lastModified = StringTools.parseHttpDate(data);
} else if (GETCONTENTLENGTH_PATH.equals(path)) {
if (this.rsrc != null) {
Long length = StringTools.parseNumber(data);
if (length != null) {
this.rsrc.contentLength = length.longValue();
}
}
}
}
/**
* Handle SAX characters notification.
*/
public void characters(char buffer[], int offset, int length) {
if (this.buff != null) this.buff.append(buffer, offset, length);
}
/**
* Reset the current characters buffer and return it as a
* {@link String}.
*/
private String resetBuffer() {
if (this.buff == null) return null;
if (this.buff.length() == 0) {
this.buff = null;
return null;
}
final String value = this.buff.toString();
this.buff = null;
return value;
}
}
/**
* A simple class holding the core resource properties.
*/
private static class Resource {
private Location location = null;
private boolean collection = false;
private long contentLength = -1;
private String contentType = null;
private Date lastModified = null;
private Date creationDate = null;
}
/* ====================================================================== */
/* COMMAND LINE CLIENT */
/* ====================================================================== */
/**
* A command-line interface to a WebDAV repository.
*
* When invoked from the command line, this class requires one only
* argument, the URL location of the WebDAV repository to connect to.
*
* After connection this method will interact with the user using an
* extremely simple console-based interface.
*/
public static void main(String args[])
throws IOException {
final InputStreamReader r = new InputStreamReader(System.in);
final BufferedReader in = new BufferedReader(r);
WebDavClient client = new WebDavClient(Location.parse(args[0]));
while (true) try {
System.out.print("[" + client.getLocation() + "] -> ");
args = parse(in.readLine());
if (args == null) break;
if (args[0].equals("list")) {
if (args[1] == null) list(client, System.out);
else list(client.open(args[1]), System.out);
} else if (args[0].equals("refresh")) {
client = client.refresh();
} else if (args[0].equals("get")) {
if (args[1] != null) {
final InputStream input = client.get(args[1]);
final File file = new File(args[2]).getCanonicalFile();
final OutputStream output = new FileOutputStream(file);
final long bytes = StreamTools.copy(input, output);
System.out.println("Fetched child \"" + args[1] +
"\" to file \"" + file + "\" (" +
bytes + " bytes)");
}
else System.out.print("Can't \"get\" null");
} else if (args[0].equals("put")) {
if (args[1] != null) {
final File file = new File(args[1]).getCanonicalFile();
final InputStream input = new FileInputStream(file);
final OutputStream output = client.put(args[2], file.length());
final long bytes = StreamTools.copy(input, output);
System.out.println("Uploaded file \"" + file +
"\" to child \"" + args[2] + "\" (" +
bytes + " bytes)");
}
else System.out.print("Can't \"put\" null");
} else if (args[0].equals("mkcol")) {
if (args[1] != null) {
client.mkcol(args[1]);
System.out.println("Created \"" + args[1] + "\"");
}
else System.out.print("Can't \"mkcol\" null");
} else if (args[0].equals("delete")) {
if (args[1] != null) {
client.delete(args[1]);
System.out.println("Deleted \"" + args[1] + "\"");
}
else System.out.print("Can't \"delete\" null");
} else if (args[0].equals("cd")) {
if (args[1] != null) client = client.open(args[1]);
else System.out.print("Can't \"cd\" to null");
} else if (args[0].equals("quit")) {
break;
} else {
System.out.print("Invalid command \"" + args[0] + "\". ");
System.out.println("Valid commands are:");
System.out.println(" - \"list\" list the children child");
System.out.println(" - \"get\" fetch the specified child");
System.out.println(" - \"put\" put the specified child");
System.out.println(" - \"mkcol\" create a collection");
System.out.println(" - \"delete\" delete a child");
System.out.println(" - \"put\" put the specified resource");
System.out.println(" - \"cd\" change the location");
System.out.println(" - \"refresh\" refresh this location");
System.out.println(" - \"quit\" quit this application");
}
} catch (Exception exception) {
exception.printStackTrace(System.err);
}
System.err.println();
}
/**
* Parse a line entered by the user returning a three-tokens argument
* list (command, argument 1, argument 2)
*/
private static String[] parse(String line) {
if (line == null) return null;
final String array[] = new String[3];
final StringTokenizer tokenizer = new StringTokenizer(line);
int offset = 0;
while (tokenizer.hasMoreTokens() && (offset < 3))
array[offset ++] = tokenizer.nextToken();
if (array[0] == null) return null;
if (array[2] == null) array[2] = array[1];
return array;
}
/**
* Pseudo-nicely display a list of the children of a collection
*/
private static void list(WebDavClient client, PrintStream out)
throws IOException {
out.print("C | ");
out.print("CONTENT TYPE | ");
out.print("CREATED | ");
out.print("MODIFIED | ");
out.print("SIZE | ");
out.println("NAME ");
for (Iterator iterator = client.iterator(); iterator.hasNext() ; ) {
final StringBuffer buffer = new StringBuffer();
String child = (String) iterator.next();
if (client.isCollection(child)) buffer.append("* | ");
else buffer.append(" | ");
format(buffer, client.getContentType(child), 15).append(" | ");
format(buffer, client.getCreationDate(child), 19).append(" | ");
format(buffer, client.getLastModified(child), 19).append(" | ");
format(buffer, client.getContentLength(child), 10).append(" | ");
out.println(buffer.append(child));
}
}
/** Format a number aligning it to the right of a string.
*/
private static StringBuffer format(StringBuffer buf, long num, int len) {
final String data;
if (num < 0) data = "";
else data = Long.toString(num);
final int spaces = len - data.length();
for (int x = 0; x < spaces; x++) buf.append(' ');
buf.append(data);
return buf;
}
/** Format a string into an exact number of characters.
*/
private static StringBuffer format(StringBuffer buf, Object obj, int len) {
final String string;
if (obj == null) {
string = ("[null]");
} else if (obj instanceof Date) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
string = f.format((Date) obj);
} else {
string = obj.toString();
}
final StringBuffer buffer = new StringBuffer(string);
for (int x = string.length(); x < len; x ++) buffer.append(' ');
return buf.append(buffer.substring(0, len));
}
}