blob: e73fb38716bb7edd0298dbd0f1ffb0d9a8ac7efa [file] [log] [blame]
/*
* 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.
*/
package org.apache.cocoon.components.source.impl;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.Iterator;
import org.apache.avalon.excalibur.datasource.DataSourceComponent;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.ServiceSelector;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceNotFoundException;
import org.apache.excalibur.source.SourceValidity;
import org.apache.cocoon.CascadingIOException;
/**
* A <code>Source</code> that takes its content in a single JDBC column. Any
* kind of column can be used (clob, blob, varchar, etc), but "Blob" means
* that the whole content is contained in a single column.
* <p>The URL syntax is "blob:/datasource/table/column[cond]", where :
* <ul>
* <li>"datasource" is the jdbc datasource to use (defined in cocoon.xonf)
* <li>"table" is the database table,
* <li>"column" is (you can guess, now :) the column in "table",
* <li>"cond" is the boolean condition used to select a particular record in
* the table.
* </ul>
* <p>For example, "<code>blob:/personel/people/photo[userid='foo']</code>"
* will fetch the first column returned by the statement "<code>SELECT photo
* from people where userid='foo'</code>" in the datasource "<code>personel</code>"
*
* @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
* @author <a href="mailto:stephan@apache.org">Stephan Michels</a>
* @version CVS $Id$
*/
public class BlobSource extends AbstractLogEnabled
implements Source, Serviceable {
private static final String URL_PREFIX = "blob:/";
private static final int URL_PREFIX_LEN = URL_PREFIX.length();
/** The ServiceManager instance */
private ServiceManager manager;
/** The system ID for this source */
private String systemId;
private String datasourceName;
private String tableName;
private String columnName;
private String condition;
/**
* Create a file source from a 'blob:' url and a component manager.
* <p>The url is of the form "blob:/datasource/table/column[condition]
*/
public BlobSource(String url) throws MalformedURLException {
this.systemId = url;
// Parse the url
int start = URL_PREFIX_LEN;
// Datasource
int end = url.indexOf('/', start);
if (end == -1) {
throw new MalformedURLException("Malformed blob source (cannot find datasource) : " + url);
}
this.datasourceName = url.substring(start, end);
// Table
start = end + 1;
end = url.indexOf('/', start);
if (end == -1) {
throw new MalformedURLException("Malformed blob source (cannot find table name) : " + url);
}
this.tableName = url.substring(start, end);
// Column
start = end + 1;
end = url.indexOf('[', start);
if (end == -1) {
this.columnName = url.substring(start);
} else {
this.columnName = url.substring(start, end);
// Condition
start = end + 1;
end = url.length() - 1;
if (url.charAt(end) != ']') {
throw new MalformedURLException("Malformed url for a blob source (cannot find condition) : " + url);
} else {
this.condition = url.substring(start, end);
}
}
}
/**
* Set the current <code>ServiceManager</code> instance used by this
* <code>Serviceable</code>.
*/
public void service(ServiceManager manager) {
this.manager = manager;
}
/**
* Return the protocol
*/
public String getScheme() {
return URL_PREFIX;
}
/**
* Return the unique identifer for this source
*/
public String getURI() {
return this.systemId;
}
/**
* Get the input stream for this source.
*/
public InputStream getInputStream() throws IOException, SourceException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Opening stream for datasource " + this.datasourceName +
", table " + this.tableName + ", column " + this.columnName +
(this.condition == null ? ", no condition" : ", condition " + this.condition)
);
}
Connection conn = null;
Statement stmt = null;
try {
conn = getConnection();
stmt = conn.createStatement();
StringBuffer selectBuf = new StringBuffer("SELECT ").append(this.columnName).
append(" FROM ").append(this.tableName);
if (this.condition != null) {
selectBuf.append(" WHERE ").append(this.condition);
}
String select = selectBuf.toString();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Executing statement " + select);
}
ResultSet rs = stmt.executeQuery(select);
if (!rs.next()) {
throw new SourceNotFoundException("Source not found.");
}
int colType = rs.getMetaData().getColumnType(1);
switch(colType) {
case Types.BLOB :
Blob blob = rs.getBlob(1);
if (blob != null) {
return new JDBCInputStream(blob.getBinaryStream(), conn);
}
break;
case Types.CLOB :
Clob clob = rs.getClob(1);
if (clob != null) {
return new JDBCInputStream(clob.getAsciiStream(), conn);
}
break;
default :
String value = rs.getString(1);
if (value != null) {
return new ByteArrayInputStream(value.getBytes());
}
}
return new ByteArrayInputStream(new byte[0]);
} catch (SQLException e) {
String msg = "Cannot retrieve content from " + this.systemId;
getLogger().error(msg, e);
// IOException would be more adequate, but SourceException is cascaded...
throw new SourceException(msg, e);
} finally {
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) { /* ignored */ }
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) { /* ignored */ }
}
}
/**
* Get the Validity object. This can either wrap the last modification
* date or the expires information or...
* If it is currently not possible to calculate such an information
* <code>null</code> is returned.
*/
public SourceValidity getValidity() {
return null;
}
/**
* Refresh the content of this object after the underlying data
* content has changed.
*/
public void refresh() {
}
/**
*
* @see org.apache.excalibur.source.Source#exists()
*/
public boolean exists() {
// FIXME
return true;
}
/**
* The mime-type of the content described by this object.
* If the source is not able to determine the mime-type by itself
* this can be <code>null</code>.
*/
public String getMimeType() {
return null;
}
/**
* Return the content length of the content or -1 if the length is
* unknown
*/
public long getContentLength() {
return -1;
}
/**
* Get the last modification date.
* @return The last modification in milliseconds since January 1, 1970 GMT
* or 0 if it is unknown
*/
public long getLastModified() {
return 0;
}
/**
* Get the value of a parameter.
* Using this it is possible to get custom information provided by the
* source implementation, like an expires date, HTTP headers etc.
*/
public String getParameter(String name) {
return null;
}
/**
* Get the value of a parameter.
* Using this it is possible to get custom information provided by the
* source implementation, like an expires date, HTTP headers etc.
*/
public long getParameterAsLong(String name) {
return 0;
}
/**
* Get parameter names
* Using this it is possible to get custom information provided by the
* source implementation, like an expires date, HTTP headers etc.
*/
public Iterator getParameterNames() {
return new EmptyIterator();
}
static class EmptyIterator implements Iterator {
public boolean hasNext() { return false; }
public Object next() { return null; }
public void remove() {}
}
private Connection getConnection() throws SourceException {
ServiceSelector selector = null;
DataSourceComponent datasource = null;
try {
try {
selector = (ServiceSelector)this.manager.lookup(DataSourceComponent.ROLE + "Selector");
datasource = (DataSourceComponent)selector.select(this.datasourceName);
} catch (Exception e) {
String msg = "Cannot get datasource '" + this.datasourceName + "'";
getLogger().error(msg);
throw new SourceException(msg, e);
}
try {
return datasource.getConnection();
} catch (Exception e) {
String msg = "Cannot get connection for datasource '" + this .datasourceName + "'";
getLogger().error(msg);
throw new SourceException(msg, e);
}
} finally {
if (datasource != null) {
selector.release(datasource);
}
}
}
/**
* An OutputStream that will close the connection that created it on
* close.
*/
private class JDBCInputStream extends FilterInputStream {
private Connection cnx;
private void closeCnx() throws IOException {
if (this.cnx != null) {
try {
cnx.close();
} catch (Exception e) {
String msg = "Error closing the connection for " + BlobSource.this.getURI();
BlobSource.this.getLogger().warn(msg, e);
throw new CascadingIOException(msg + " : " + e.getMessage(), e);
} finally {
cnx = null;
}
}
}
public JDBCInputStream(InputStream stream, Connection cnx) {
super(stream);
this.cnx = cnx;
}
public int read() throws IOException {
try {
int result = in.read();
if (result == -1) {
closeCnx();
}
return result;
} catch(IOException e) {
closeCnx();
throw e;
}
}
public int read(byte[] b) throws IOException {
try {
int result = in.read(b);
if (result == -1) {
closeCnx();
}
return result;
} catch(IOException e) {
closeCnx();
throw e;
}
}
public int read(byte[] b, int off, int len) throws IOException {
try {
int result = in.read(b, off, len);
if (result == -1) {
closeCnx();
}
return result;
} catch(IOException e) {
closeCnx();
throw e;
}
}
public void close() throws IOException {
super.close();
closeCnx();
}
}
}