/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*
* $Id: URIMapper.java 511426 2007-02-25 03:25:02Z vgritsenko $
*/
package org.apache.xindice.core.request;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.Container;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.Database;
import org.apache.xindice.core.FaultCodes;
import org.apache.xindice.util.ObjectPool;
import org.apache.xindice.util.Poolable;
import org.apache.xindice.util.XindiceException;
import org.apache.xindice.xml.TextWriter;
import org.w3c.dom.Document;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Properties;
/**
* URIMapper maps a URI (from whence it came) to a Xindice object. Xindice
* URIs can identify any of several different object types, each of which
* exposes a different (or slightly different) interface to the outside
* world.
*
* @version $Revision: 511426 $, $Date: 2007-02-24 22:25:02 -0500 (Sat, 24 Feb 2007) $
*/
public final class URIMapper extends URLConnection implements Poolable {
private static final Log log = LogFactory.getLog(URIMapper.class);
public static final int UNKNOWN = -1;
public static final int APPLICATION = 1;
public static final int COLLECTION = 2;
public static final int DOCUMENT = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
private ObjectPool pool = null;
private String uri = null;
private int type = 0;
private byte[] buf = null;
private int pos = 0;
private char lastChar = 0;
private Database db = null;
private Collection collection = null;
private Document document = null;
private String method = null;
private Container container = null;
private Properties params = null;
private String[] args = null;
private String urlresult = null; // Holds the value of the URL resolution results (XML doc or XMLObject call results )
private boolean inputstreamset = false; // Flag to tell if the input stream has been initialized
/**
* Constructor for creating URIMapper instance using a standard URL
*/
public URIMapper(URL u) {
super(u);
try {
setURI(u.toString());
} catch (Exception e) {
// Put error code here!
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
}
}
/**
* Constructor for older URIMapper instances
*/
public URIMapper(String uri) throws XindiceException {
super(null);
setURI(uri);
}
/**
* Constructor for older URIMapper instances
*/
public URIMapper() {
super(null);
}
/**
* Opens a communications link to the resource referenced by this URL,
* if such a connection has not already been established.
*/
public void connect() {
this.connected = true;
}
/**
* Returns an input stream that reads from this open connection.
*/
public InputStream getInputStream() throws IOException {
String output = null;
// Check if the inputstream has already been initialized, if not do now, else return the contents of urlresult
if (!inputstreamset) {
try {
switch (type) {
case URIMapper.DOCUMENT:
output = TextWriter.toString(getDocument());
break;
default :
// Document type not found, output error message
throw new Exception("Content type unsupported");
}
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
}
}
// Inputstream already initialized, set output to be value of urlresult
else {
output = urlresult;
}
// Save the value of output into local variable urlresult
urlresult = output;
// Set inpustreamset flag to signal that url has already been resolved
inputstreamset = true;
// Send the result to the client, sending blank string if NULL
if (output == null) {
return new ByteArrayInputStream(new byte[0]);
} else {
return new ByteArrayInputStream(output.getBytes(DEFAULT_ENCODING));
}
}
/**
* Returns the value of the content-encoding header field.
* @return the content encoding of the resource that the URL references, or null if not known.
*/
public String getContentEncoding() {
return DEFAULT_ENCODING;
}
/**
* Returns the value of the content-type header field.
* @return the content type of the resource that the URL references, or null if not known.
*/
public String getContentType() {
// Return the docuement's content type, for now this can only be "text/xml"
return "text/xml";
}
/**
* Returns the value of the content-length header field.
* @return the content length of the resource that this connection's URL references, or -1 if the content length is not known.
*/
public int getContentLength() {
// Check to see if this document has already been resolved, if not call getInputStream
if (!inputstreamset) {
try {
this.getInputStream();
} catch (IOException e) {
return 0;
}
}
// Return the lenght of this document/XMLObject call
return urlresult.length();
}
/**
* Returns the value of the last-modified header field. The result is the number of milliseconds since January 1, 1970 GMT.
* @return the date the resource referenced by this URLConnection was last modified, or 0 if not known.
*/
public long getLastModified() {
// For now this functionality is not available, return 0
return 0;
}
public void setPool(ObjectPool pool) {
this.pool = pool;
}
public void reclaim() {
reset();
if (pool != null) {
pool.putObject(this);
}
}
/**
* reset resets the state of the URIMapper.
*/
private void reset() {
type = UNKNOWN;
lastChar = 0;
method = "";
db = null;
collection = null;
document = null;
params = null;
args = null;
container = null;
// reset urlresult and inputstreamset
urlresult = null;
inputstreamset = false;
}
/**
* setURI sets the URI for the URIMapper and parses it. The parsed
* components of a URI can be retrieved using getObjectType and any
* of the get<component> methods.
*
* @param uri The URI
*/
public void setURI(String uri) throws XindiceException {
this.uri = uri;
reset();
parse();
}
/**
* parseName parses an identifier up to any of the specified delimiters.
*
* @param delims The delimiters to use
* @return The parsed name
*/
private String parseName(String delims) {
int start = pos;
while (pos < buf.length) {
lastChar = (char) buf[pos++];
if (delims.indexOf(lastChar) != -1) {
break;
}
}
if (pos == buf.length && delims.indexOf(lastChar) == -1) {
pos++;
}
return pos > start ? new String(buf, start, pos - start - 1) : "";
}
/**
* parseParams parses a parameter set and produces either a Properties or
* String[] Object representing those parameters.
*/
private void parseParams() {
if (lastChar == '?') {
// Query String param list
params = new Properties();
String name;
String value;
String temp;
while (true) {
name = parseName("=");
if (name.length() == 0) {
break;
}
value = parseName("?&;");
temp = params.getProperty(name);
if (temp != null) {
StringBuffer sb = new StringBuffer(32);
sb.append(temp);
sb.append('\u0001');
sb.append(value);
value = sb.toString();
}
params.setProperty(name, value);
}
} else
params = new Properties();
}
/**
* parse parses the URI.
*/
private void parse() throws XindiceException {
buf = uri.getBytes();
pos = 0;
String tmp;
if (buf.length == 0) {
throw new DBException(FaultCodes.URI_EMPTY);
}
// TODO: Be Able To Handle Remote URIs
if ((char) buf[0] != '/') {
parseName(":"); // Ignore Protocol
parseName("/"); // Ignore Slash
parseName("/"); // Ignore Slash
parseName("/:"); // Ignore Host (For Now)
if (lastChar == ':') {
parseName("/"); // Ignore Port
}
} else {
pos = 1;
}
// Database check
tmp = parseName("/");
if (tmp == null) {
return;
}
db = Database.getDatabase(tmp);
if (db == null) {
return;
}
type = APPLICATION;
tmp = parseName("/(?");
int objType = getParsedObjectType(db, tmp);
// If unknown then this URI just points to db and we're done.
// Otherwise we need to keep walking down the URI.
if (objType != UNKNOWN) {
type = walkURI(db, tmp, objType);
}
if (lastChar == '?') {
parseParams();
return;
}
}
/**
* Recursive method to handle the parse of the URI and setup the instance
* objects.
*/
protected int walkURI(Collection col, String name, int objType) throws XindiceException {
switch (objType) {
case DOCUMENT:
container = col.getContainer(name);
document = container.getDocument();
return DOCUMENT;
case COLLECTION:
Collection c = col.getCollection(name);
if (c != null) {
collection = c;
String tmp = parseName("/(?");
// If we have another name recurse to handle it.
if (!tmp.equals("")) {
return walkURI(c, tmp, getParsedObjectType(c, tmp));
}
return COLLECTION;
}
default:
if (log.isWarnEnabled()) {
log.warn("invalid object type : " + objType);
}
}
return UNKNOWN;
}
/**
* Determine the type of object. If more then one object has the same name
* the order of precedence is COLLECTION - XMLOBJECT - DOCUMENT
*/
protected int getParsedObjectType(Collection col, String name) throws XindiceException {
if (col.getCollection(name) != null) {
return COLLECTION;
} else if ((col.getFiler() != null) && (col.getContainer(name) != null)) {
return DOCUMENT;
} else {
return UNKNOWN;
}
}
/**
* getObjectType returns the type of Object that was identified in the
* parsing of the URI. This method will return one of the following
* values: UNKNOWN, APPLICATION, DATABASE, COLLECTION, DOCUMENT or
* XMLOBJECT.
*
* @return The object type
*/
public int getObjectType() {
return type;
}
/**
* getDatabase returns the Database that was resolved in the
* parsing of this URI.
*
* @return The Database
*/
public Database getDatabase() {
return db;
}
/**
* getCollection returns the Collection object that was resolved in the
* parsing of the URI. If no Collection was resolved, this method will
* return null.
*
* @return The Collection
*/
public Collection getCollection() {
return collection;
}
/**
* getDocument returns the Document object that was resolved in the
* parsing of the URI. If no Document was resolved, this method will
* return null.
*
* @return The Document
*/
public Document getDocument() {
return document;
}
/**
* getContainer returns the Document Container that was resolved in
* the parsing of the URI. If no Container was resolved, this method
* will return null.
*
* @return The Container
*/
public Container getContainer() {
return container;
}
/**
* getMethod returns the method name that was resolved in the parsing
* of the URI. Method names are associated with XMLObjects.
* If no method name was resolved, this method will return null.
*
* @return The method name
*/
public String getMethod() {
return method;
}
/**
* getProperties returns the Properties object that was produced in
* parsing the URI's Query String. Properties are passed into methods
* that are associated with XMLObjects. This method will return null if
* no Properties were resolved.
*
* @return The Query String Properties
*/
public Properties getProperties() {
return params;
}
/**
* getArguments returns method arguments in the form of a String array.
* Method arguments are passed to a method in the order that they are
* specified in the URI. If no arguments were parsed, this method will
* return null.
*
* @return The method arguments
*/
public String[] getArguments() {
return args;
}
}