
org.enhydra.xml.xmlc.misc.SSIReader Maven / Gradle / Ivy
/*
* Enhydra Java Application Server Project
*
* The contents of this file are subject to the Enhydra Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License on
* the Enhydra web site ( http://www.enhydra.org/ ).
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific terms governing rights and limitations
* under the License.
*
* The Initial Developer of the Enhydra Application Server is Lutris
* Technologies, Inc. The Enhydra Application Server and portions created
* by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
* All Rights Reserved.
*
* Contributor(s):
*
* $Id: SSIReader.java,v 1.8 2005/02/14 02:09:24 taweili Exp $
*/
package org.enhydra.xml.xmlc.misc;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import org.xml.sax.InputSource;
//FIXME: Keeping the LineNumberMap maybe inefficient. We might want to
//drop this in favor of just tracking the current line number (and maybe
//one back. Need to see how the parser behaves. Plus, with this class
//reading everything into memory, we could just lines when actually
//needed. Also, all of the line number map stuff isn't abstracted.
/**
* A reader that implements Server-Side Includes (SSI). This used the syntax
* defined by Apache mod_include, however it only implements the `include'
* directive.
*
* This input stream automatically closes on reaching EOF.
*/
public final class SSIReader extends Reader {
/**
* Listener for SSI open/close events.
*/
public class Listener {
/** Classed when a new include file is opened. */
}
/**
* Include command and arguments.
*/
private static final String INCLUDE = "include";
private static final String INCLUDE_FILE = "file";
private static final String INCLUDE_VIRTUAL = "virtual";
private static final String[] INCLUDE_VALID_ARGS = {
INCLUDE_FILE,
INCLUDE_VIRTUAL
};
/**
* The ssi-base set
*/
private String ssiBase = null;
/**
* Input stream we are reading from. Also the top of the stack,
* if others have been included.
*/
private SSIParsedStream fIn;
/**
* Map of stream position to source file and line number.
*/
private LineNumberRecorder fLineNumbers;
/**
* Construct a new reader for the specified file.
*/
//public SSIReader(InputSource source) throws IOException {
public SSIReader(InputSource source, String ssiBase) throws IOException {
fLineNumbers = new LineNumberRecorder();
fIn = new SSIParsedStream(source, fLineNumbers);
this.ssiBase = ssiBase;
}
/**
* Get the system id of the currently opened file.
*/
public String getSystemId() {
if (fIn != null) {
return fIn.getSystemId();
} else {
return null;
}
}
/**
* Open a SSI include file, pushing on the stack.
*/
private void openSSIInclude(String includingFileName,
String fileName) throws IOException {
// Get path to file, relative to source directory
if (ssiBase != null) {
// The following maybe written better, but I think
// SSI-syntax needs the leading slash, so it should work.
if (fileName.startsWith("/")) {
fileName = ssiBase + File.separator + fileName;
}
}
String systemId = "";
try {
File includingFile = new File(includingFileName);
includingFile. getCanonicalPath(); //Fail fast (IOException) if we are dealing with a URL
File inclFile = new File(fileName);
File baseDir = null;
if (!inclFile.isAbsolute()) {
baseDir = includingFile.getParentFile();
}
File inclPath = new File(baseDir, fileName);
systemId = inclPath.toString(); // Problem with toURL().getExternalForm(). It returns strange URL
} catch (IOException ioe) {
try {
String baseDir = "";
if (!fileName.startsWith("/")) {
//TODO - revisit! This tries to mimick the above,
//but what would this mean for the "file:"
//protocol? Where would "/index.ssi" be located
//when there is no domain name to be relative to
//in the "file:" protocol? The URL would be
//invalid. And we aren't getting a "file:"
//protocol on the fileName itself. Unless this is
//re-written, we only support relative SSI's and
//this check is a bit pointless.
baseDir = includingFileName.substring(0, includingFileName.lastIndexOf("/") + 1);
}
URL url = new URL(baseDir + fileName);
systemId = url.toExternalForm();
} catch (IOException ioe2) {
throw ioe2;
}
}
// Open new reader, push current one
fIn = new SSIParsedStream(new InputSource(systemId),
fLineNumbers, fIn);
}
/**
* Process a SSI include directive.
*/
private void processSSIInclude(SSIDirective directive) throws IOException {
// Validate
directive.validateArgumentNames(INCLUDE_VALID_ARGS);
if (directive.getNumArgs() < 1) {
throw new IOException("SSI include must have at least one argument");
}
// Open includes. Done in reverse, as they are pushed on a stack
for (int idx = directive.getNumArgs()-1; idx >= 0; idx--) {
openSSIInclude(directive.getSystemId(), directive.getArgValue(idx));
}
}
/**
* Process the SSI directive that is next in the stream.
*/
private void processSSIDirective() throws IOException {
SSIDirective directive = fIn.parseSSIDirective();
if (directive.getCmd().equals(INCLUDE)) {
processSSIInclude(directive);
} else {
throw new IOException("Invalid or unsupport SSI command \""
+ directive.getCmd() + "\": "
+ fIn.getSystemId());
}
}
/**
* Read a character.
* @return The character, -1 if no more characters are available.
*/
public int read() throws IOException {
int ch;
do {
ch = fIn.read();
if (ch == SSIParsedStream.AT_EOF) {
fIn = fIn.pop();
if (fIn == null) {
return -1; // EOF
}
} else if (ch == SSIParsedStream.AT_SSI) {
processSSIDirective();
}
} while (ch < 0);
return ch;
}
/**
* Read characters into a portion of an array. To simplify the
* handling of an SSI directive, a directive is only processed if
* it's at the start of the buffer. SSIs in the middle of the
* read result in a partial read.
*
* @see java.io.FilterReader#read
* @exception IOException If an I/O error occurs
*/
public int read(char cbuf[], int off, int len) throws IOException {
int readLen;
do {
readLen = fIn.read(cbuf, off, len);
if (readLen == SSIParsedStream.AT_EOF) {
fIn = fIn.pop();
if (fIn == null) {
return -1; // EOF
}
} else if (readLen == SSIParsedStream.AT_SSI) {
// Directive might change fIn to a new file.
processSSIDirective();
}
} while (readLen < 0);
return readLen;
}
/**
* Close the stream.
*
* @exception IOException If an I/O error occurs
*/
public void close() throws IOException {
fIn = null;
}
/**
* Get the line number map.
*/
public final LineNumberMap getLineNumberMap() {
return fLineNumbers;
}
/**
* Construct an InputSource containing an SSI reader.
*/
// dbr_20020128.4_start
//public static InputSource create(InputSource inputSource) throws IOException {
public static InputSource create(InputSource inputSource, String ssiBase) throws IOException {
InputSource ssiSource
//= new InputSource(new SSIReader(inputSource));
= new InputSource(new SSIReader(inputSource, ssiBase));
// dbr_20020128.4_end
ssiSource.setPublicId(inputSource.getPublicId());
ssiSource.setSystemId(inputSource.getSystemId());
ssiSource.setEncoding(inputSource.getEncoding());
return ssiSource;
}
}