0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010: package org.mmbase.storage.implementation.database;
0011:
0012: import java.io.*;
0013: import java.sql.*;
0014: import java.util.*;
0015:
0016: import org.mmbase.bridge.Field;
0017: import org.mmbase.bridge.NodeManager;
0018: import org.mmbase.cache.Cache;
0019: import org.mmbase.core.CoreField;
0020: import org.mmbase.core.util.Fields;
0021: import org.mmbase.module.core.*;
0022: import org.mmbase.storage.*;
0023: import org.mmbase.storage.util.*;
0024: import org.mmbase.util.Casting;
0025: import org.mmbase.util.logging.Logger;
0026: import org.mmbase.util.logging.Logging;
0027: import org.mmbase.util.transformers.CharTransformer;
0028:
0029: /**
0030: * A JDBC implementation of an object related storage manager.
0031: * @javadoc
0032: *
0033: * @author Pierre van Rooden
0034: * @since MMBase-1.7
0035: * @version $Id: DatabaseStorageManager.java,v 1.189 2008/02/18 12:43:17 michiel Exp $
0036: */
0037: public class DatabaseStorageManager implements StorageManager {
0038:
0039: /** Max size of the object type cache */
0040: public static final int OBJ2TYPE_MAX_SIZE = 20000;
0041:
0042: // contains a list of buffered keys
0043: protected static final List<Integer> sequenceKeys = new LinkedList<Integer>();
0044:
0045: private static final Logger log = Logging
0046: .getLoggerInstance(DatabaseStorageManager.class);
0047:
0048: private static final Blob BLOB_SHORTED = new InputStreamBlob(null,
0049: -1);
0050:
0051: private static final CharTransformer UNICODE_ESCAPER = new org.mmbase.util.transformers.UnicodeEscaper();
0052:
0053: // maximum size of the key buffer
0054: private static Integer bufferSize = null;
0055:
0056: /**
0057: * This sets contains all existing tables which could by associated with MMBase builders. This
0058: * is because they are queried all at once, but requested for existance only one at a time.
0059: * @since MMBase-1.7.4
0060: */
0061: private static Set<String> tableNameCache = null;
0062:
0063: /**
0064: * This sets contains all verified tables.
0065: * @since MMBase-1.8.1
0066: */
0067: private static Set<String> verifiedTablesCache = new HashSet<String>();
0068:
0069: /**
0070: * Whether the warning about blob on legacy location was given.
0071: */
0072: private static boolean legacyWarned = false;
0073:
0074: /**
0075: * The cache that contains the last X types of all requested objects
0076: * @since 1.7
0077: */
0078: protected static Cache<Integer, Integer> typeCache;
0079:
0080: static {
0081: typeCache = new Cache(OBJ2TYPE_MAX_SIZE) {
0082: public String getName() {
0083: return "TypeCache";
0084: }
0085:
0086: public String getDescription() {
0087: return "Cache for node types";
0088: }
0089: };
0090: typeCache.putCache();
0091: }
0092:
0093: /**
0094: * The factory that created this manager
0095: */
0096: protected DatabaseStorageManagerFactory factory;
0097:
0098: /**
0099: * The currently active Connection.
0100: * This member is set by {!link #getActiveConnection()} and unset by {@link #releaseActiveConnection()}
0101: */
0102: protected Connection activeConnection;
0103:
0104: /**
0105: * <code>true</code> if a transaction has been started.
0106: * This member is for state maitenance and may be true even if the storage does not support transactions
0107: */
0108: protected boolean inTransaction = false;
0109:
0110: /**
0111: * The transaction issolation level to use when starting a transaction.
0112: * This value is retrieved from the factory's {@link Attributes#TRANSACTION_ISOLATION_LEVEL} attribute, which is commonly set
0113: * to the highest (most secure) transaction isolation level available.
0114: */
0115: protected int transactionIsolation = Connection.TRANSACTION_NONE;
0116:
0117: /**
0118: * Pool of changed nodes in a transaction
0119: */
0120: protected final Map<MMObjectNode, String> changes = new HashMap<MMObjectNode, String>();
0121:
0122: /**
0123: * Constructor
0124: */
0125: public DatabaseStorageManager() {
0126: }
0127:
0128: protected long getLogStartTime() {
0129: return System.currentTimeMillis();
0130: }
0131:
0132: // for debug purposes
0133: protected final void logQuery(String query, long startTime) {
0134: if (log.isDebugEnabled()) {
0135: long now = System.currentTimeMillis();
0136: log.debug("Time:" + (now - startTime) + " Query :" + query);
0137: if (log.isTraceEnabled()) {
0138: log.trace(Logging.stackTrace());
0139: }
0140: }
0141: }
0142:
0143: // javadoc is inherited
0144: public double getVersion() {
0145: return 1.0;
0146: }
0147:
0148: // javadoc is inherited
0149: public void init(StorageManagerFactory factory)
0150: throws StorageException {
0151: this .factory = (DatabaseStorageManagerFactory) factory;
0152: if (factory.supportsTransactions()) {
0153: transactionIsolation = ((Integer) factory
0154: .getAttribute(Attributes.TRANSACTION_ISOLATION_LEVEL))
0155: .intValue();
0156: }
0157: // determine generated key buffer size
0158: if (bufferSize == null) {
0159: bufferSize = 1;
0160: Object bufferSizeAttribute = factory
0161: .getAttribute(Attributes.SEQUENCE_BUFFER_SIZE);
0162: if (bufferSizeAttribute != null) {
0163: try {
0164: bufferSize = Integer.valueOf(bufferSizeAttribute
0165: .toString());
0166: } catch (NumberFormatException nfe) {
0167: // remove the SEQUENCE_BUFFER_SIZE attribute (invalid value)
0168: factory.setAttribute(
0169: Attributes.SEQUENCE_BUFFER_SIZE, null);
0170: log
0171: .error("The attribute 'SEQUENCE_BUFFER_SIZE' has an invalid value("
0172: + bufferSizeAttribute
0173: + "), will be ignored.");
0174: }
0175: }
0176: }
0177:
0178: }
0179:
0180: /**
0181: * Obtains an active connection, opening a new one if needed.
0182: * This method sets and then returns the {@link #activeConnection} member.
0183: * If an active connection was allready open, and the manager is in a database transaction, that connection is returned instead.
0184: * Otherwise, the connection is closed before a new one is opened.
0185: * @throws SQLException if opening the connection failed
0186: */
0187: protected Connection getActiveConnection() throws SQLException {
0188: if (activeConnection != null) {
0189: if (factory.supportsTransactions() && inTransaction) {
0190: return activeConnection;
0191: } else {
0192: releaseActiveConnection();
0193: }
0194: }
0195: activeConnection = factory.getDataSource().getConnection();
0196: // set autocommit to true
0197: if (activeConnection != null) {
0198: activeConnection.setAutoCommit(true);
0199: }
0200: return activeConnection;
0201: }
0202:
0203: /**
0204: * Safely closes the active connection.
0205: * If a transaction has been started, the connection is not closed.
0206: */
0207: protected void releaseActiveConnection() {
0208: if (!(inTransaction && factory.supportsTransactions())
0209: && activeConnection != null) {
0210: try {
0211: // ensure that future attempts to obtain a connection (i.e.e if it came from a pool)
0212: // start with autocommit set to true
0213: // needed because Query interface does not use storage layer to obtain transactions
0214: activeConnection.setAutoCommit(true);
0215: activeConnection.close();
0216: } catch (SQLException se) {
0217: // if something went wrong, log, but do not throw exceptions
0218: log.error("Failure when closing connection: "
0219: + se.getMessage());
0220: }
0221: activeConnection = null;
0222: }
0223: }
0224:
0225: // javadoc is inherited
0226: public void beginTransaction() throws StorageException {
0227: if (inTransaction) {
0228: throw new StorageException(
0229: "Cannot start Transaction when one is already active.");
0230: } else {
0231: if (factory.supportsTransactions()) {
0232: try {
0233: getActiveConnection();
0234: if (activeConnection == null)
0235: return;
0236: activeConnection
0237: .setTransactionIsolation(transactionIsolation);
0238: activeConnection.setAutoCommit(false);
0239: } catch (SQLException se) {
0240: releaseActiveConnection();
0241: inTransaction = false;
0242: throw new StorageException(se);
0243: }
0244: }
0245: inTransaction = true;
0246: changes.clear();
0247: }
0248:
0249: }
0250:
0251: // javadoc is inherited
0252: public void commit() throws StorageException {
0253: if (!inTransaction) {
0254: throw new StorageException("No transaction started.");
0255: } else {
0256: inTransaction = false;
0257: if (factory.supportsTransactions()) {
0258: if (activeConnection == null) {
0259: throw new StorageException("No active connection");
0260: }
0261:
0262: try {
0263: activeConnection.commit();
0264: } catch (SQLException se) {
0265: throw new StorageException(se);
0266: } finally {
0267: releaseActiveConnection();
0268: factory.getChangeManager().commit(changes);
0269: }
0270: }
0271: }
0272: }
0273:
0274: // javadoc is inherited
0275: public boolean rollback() throws StorageException {
0276: if (!inTransaction) {
0277: throw new StorageException("No transaction started.");
0278: } else {
0279: inTransaction = false;
0280: if (factory.supportsTransactions()) {
0281: try {
0282: activeConnection.rollback();
0283: } catch (SQLException se) {
0284: throw new StorageException(se);
0285: } finally {
0286: releaseActiveConnection();
0287: changes.clear();
0288: }
0289: }
0290: return factory.supportsTransactions();
0291: }
0292: }
0293:
0294: /**
0295: * Commits the change to a node.
0296: * If the manager is in a transaction (and supports it), the change is stored in a
0297: * {@link #changes} object (to be committed after the transaction ends).
0298: * Otherwise it directly commits and broadcasts the changes
0299: * @param node the node to register
0300: * @param change the type of change: "n": new, "c": commit, "d": delete, "r" : relation changed
0301: */
0302: protected void commitChange(MMObjectNode node, String change) {
0303: if (inTransaction && factory.supportsTransactions()) {
0304: changes.put(node, change);
0305: } else {
0306: factory.getChangeManager().commit(node, change);
0307: log.debug("Commited node");
0308: }
0309: }
0310:
0311: public int createKey() throws StorageException {
0312: log.debug("Creating key");
0313: synchronized (sequenceKeys) {
0314: log.debug("Acquired lock");
0315: // if sequenceKeys conatins (buffered) keys, return this
0316: if (sequenceKeys.size() > 0) {
0317: return sequenceKeys.remove(0);
0318: } else {
0319: String query = "";
0320: try {
0321: getActiveConnection();
0322: Statement s;
0323: Scheme scheme = factory.getScheme(
0324: Schemes.UPDATE_SEQUENCE,
0325: Schemes.UPDATE_SEQUENCE_DEFAULT);
0326: if (scheme != null) {
0327: query = scheme.format(this , factory
0328: .getStorageIdentifier("number"),
0329: bufferSize);
0330: long startTime = getLogStartTime();
0331: s = activeConnection.createStatement();
0332: s.executeUpdate(query);
0333: s.close();
0334: logQuery(query, startTime);
0335: }
0336: scheme = factory.getScheme(Schemes.READ_SEQUENCE,
0337: Schemes.READ_SEQUENCE_DEFAULT);
0338: query = scheme
0339: .format(this , factory
0340: .getStorageIdentifier("number"),
0341: bufferSize);
0342: s = activeConnection.createStatement();
0343: try {
0344: long startTime = getLogStartTime();
0345: ResultSet result = s.executeQuery(query);
0346: logQuery(query, startTime);
0347: try {
0348: if (result.next()) {
0349: int keynr = result.getInt(1);
0350: // add remaining keys to sequenceKeys
0351: for (int i = 1; i < bufferSize
0352: .intValue(); i++) {
0353: sequenceKeys.add(keynr + i);
0354: }
0355: return keynr;
0356: } else {
0357: throw new StorageException(
0358: "The sequence table is empty.");
0359: }
0360: } finally {
0361: result.close();
0362: }
0363: } finally {
0364: s.close();
0365: }
0366: } catch (SQLException se) {
0367: log.error("" + query + " " + se.getMessage(), se);
0368: // wait 2 seconds, so any locks that were claimed are released.
0369: try {
0370: Thread.sleep(2000);
0371: } catch (InterruptedException re) {
0372: }
0373: throw new StorageException(se);
0374: } finally {
0375: releaseActiveConnection();
0376: }
0377: }
0378: }
0379: }
0380:
0381: // javadoc is inherited
0382: public String getStringValue(MMObjectNode node, CoreField field)
0383: throws StorageException {
0384: try {
0385: MMObjectBuilder builder = node.getBuilder();
0386: Scheme scheme = factory.getScheme(Schemes.GET_TEXT_DATA,
0387: Schemes.GET_TEXT_DATA_DEFAULT);
0388: String query = scheme.format(this , builder, field, builder
0389: .getField("number"), node);
0390: getActiveConnection();
0391: Statement s = activeConnection.createStatement();
0392: ResultSet result = s.executeQuery(query);
0393: try {
0394: if ((result != null) && result.next()) {
0395: String rvalue = (String) getStringValue(result, 1,
0396: field, false);
0397: result.close();
0398: s.close();
0399: return rvalue;
0400: } else {
0401: if (result != null)
0402: result.close();
0403: s.close();
0404: throw new StorageException("Node with number "
0405: + node.getNumber() + " not found.");
0406: }
0407: } finally {
0408: result.close();
0409: }
0410: } catch (SQLException se) {
0411: throw new StorageException(se);
0412: } finally {
0413: releaseActiveConnection();
0414: }
0415: }
0416:
0417: /**
0418: * Retrieve a text for a specified object field.
0419: * The default method uses {@link ResultSet#getString(int)} to obtain text.
0420: * Override this method if you want to optimize retrieving large texts,
0421: * i.e by using clobs or streams.
0422: * @param result the resultset to retrieve the text from
0423: * @param index the index of the text in the resultset
0424: * @param field the (MMBase) fieldtype. This value can be null
0425: * @return the retrieved text, <code>null</code> if no text was stored
0426: * @throws SQLException when a database error occurs
0427: * @throws StorageException when data is incompatible or the function is not supported
0428: */
0429: protected Object getStringValue(ResultSet result, int index,
0430: CoreField field, boolean mayShorten)
0431: throws StorageException, SQLException {
0432: String untrimmedResult = null;
0433: if (field != null
0434: && (field.getStorageType() == Types.CLOB
0435: || field.getStorageType() == Types.BLOB || factory
0436: .hasOption(Attributes.FORCE_ENCODE_TEXT))) {
0437: InputStream inStream = result.getBinaryStream(index);
0438: if (result.wasNull()) {
0439: return null;
0440: }
0441: if (mayShorten && shorten(field)) {
0442: return MMObjectNode.VALUE_SHORTED;
0443: }
0444: try {
0445: ByteArrayOutputStream bytes = new ByteArrayOutputStream();
0446: int c = inStream.read();
0447: while (c != -1) {
0448: bytes.write(c);
0449: c = inStream.read();
0450: }
0451: inStream.close();
0452: String encoding = factory.getMMBase().getEncoding();
0453: if (encoding.equalsIgnoreCase("ISO-8859-1")) {
0454: // CP 1252 only fills in the 'blanks' of ISO-8859-1,
0455: // so it is save to upgrade the encoding, in case accidentily those bytes occur
0456: encoding = "CP1252";
0457: }
0458: untrimmedResult = new String(bytes.toByteArray(),
0459: encoding);
0460: if (log.isDebugEnabled()) {
0461: log.debug("Got "
0462: + untrimmedResult
0463: + " "
0464: + new String(untrimmedResult
0465: .getBytes("ISO-8859-1"), "UTF-8")
0466: + " with " + encoding);
0467: }
0468: } catch (IOException ie) {
0469: throw new StorageException(ie);
0470: }
0471: } else {
0472: untrimmedResult = result.getString(index);
0473: if (factory.hasOption(Attributes.LIE_CP1252)
0474: && untrimmedResult != null) {
0475: try {
0476: String encoding = factory.getMMBase().getEncoding();
0477: if (encoding.equalsIgnoreCase("ISO-8859-1")) {
0478: untrimmedResult = new String(untrimmedResult
0479: .getBytes("ISO-8859-1"), "CP1252");
0480: }
0481: } catch (java.io.UnsupportedEncodingException uee) {
0482: // cannot happen
0483: }
0484: }
0485: }
0486:
0487: if (untrimmedResult != null) {
0488: if (factory.hasOption(Attributes.TRIM_STRINGS)) {
0489: untrimmedResult = untrimmedResult.trim();
0490: }
0491: if (factory.getGetSurrogator() != null) {
0492: untrimmedResult = factory.getGetSurrogator().transform(
0493: untrimmedResult);
0494: }
0495: }
0496:
0497: return untrimmedResult;
0498: }
0499:
0500: /**
0501: * Retrieve the XML (as a string) for a specified object field.
0502: * The default method uses {@link ResultSet#getString(int)} to obtain text.
0503: * Unlike
0504: * Override this method if you want to optimize retrieving large texts,
0505: * i.e by using clobs or streams.
0506: * @param result the resultset to retrieve the xml from
0507: * @param index the index of the xml in the resultset
0508: * @param field the (MMBase) fieldtype. This value can be null
0509: * @return the retrieved xml as text, <code>null</code> if nothing was stored
0510: * @throws SQLException when a database error occurs
0511: * @throws StorageException when data is incompatible or the function is not supported
0512: */
0513: protected Object getXMLValue(ResultSet result, int index,
0514: CoreField field, boolean mayShorten)
0515: throws StorageException, SQLException {
0516: return getStringValue(result, index, field, mayShorten);
0517: }
0518:
0519: /**
0520: * Retrieve a date for a specified object field.
0521: * The default method uses {@link ResultSet#getTimestamp(int)} to obtain the date.
0522: * @param result the resultset to retrieve the value from
0523: * @param index the index of the value in the resultset
0524: * @param field the (MMBase) fieldtype. This value can be null
0525: * @return the retrieved java.util.Date value, <code>null</code> if no text was stored
0526: * @throws SQLException when a database error occurs
0527: * @throws StorageException when data is incompatible or the function is not supported
0528: * @since MMBase-1.8
0529: */
0530: protected java.util.Date getDateTimeValue(ResultSet result,
0531: int index, CoreField field) throws StorageException,
0532: SQLException {
0533: Timestamp ts = null;
0534: try {
0535: ts = result.getTimestamp(index);
0536: } catch (SQLException sqle) {
0537: // deal with all-zero datetimes when reading them
0538: if ("S1009".equals(sqle.getSQLState())) {
0539: return null;
0540: } else {
0541: throw sqle;
0542: }
0543: }
0544: if (ts == null) {
0545: return null;
0546: } else {
0547: long time = ts.getTime();
0548: java.util.Date d = new java.util.Date(time
0549: + factory.getTimeZoneOffset(time));
0550: return d;
0551: }
0552: }
0553:
0554: /**
0555: * Retrieve a boolean value for a specified object field.
0556: * The default method uses {@link ResultSet#getBoolean(int)} to obtain the date.
0557: * @param result the resultset to retrieve the value from
0558: * @param index the index of the value in the resultset
0559: * @param field the (MMBase) fieldtype. This value can be null
0560: * @return the retrieved Boolean value, <code>null</code> if no text was stored
0561: * @throws SQLException when a database error occurs
0562: * @throws StorageException when data is incompatible or the function is not supported
0563: * @since MMBase-1.8
0564: */
0565: protected Boolean getBooleanValue(ResultSet result, int index,
0566: CoreField field) throws StorageException, SQLException {
0567: boolean value = result.getBoolean(index);
0568: if (result.wasNull()) {
0569: return null;
0570: } else {
0571: return Boolean.valueOf(value);
0572: }
0573: }
0574:
0575: /**
0576: * Determine whether a field (such as a large text or a blob) should be shortened or not.
0577: * A 'shortened' field contains a placeholder text ('$SHORTED') to indicate that the field is expected to be of large size
0578: * and should be retrieved by an explicit call to {@link #getStringValue(MMObjectNode, CoreField)} or.
0579: * {@link #getBinaryValue(MMObjectNode, CoreField)}.
0580: * The default implementation returns <code>true</code> for binaries, and <code>false</code> for other
0581: * types.
0582: * Override this method if you want to be able to change the placeholder strategy.
0583: * @param field the (MMBase) fieldtype
0584: * @return <code>true</code> if the field should be shortened
0585: * @throws SQLException when a database error occurs
0586: * @throws StorageException when data is incompatible or the function is not supported
0587: */
0588: protected boolean shorten(CoreField field) {
0589: return field.getType() == Field.TYPE_BINARY;
0590: }
0591:
0592: /**
0593: * Read a binary (blob) from a field in the database
0594: * @param node the node the binary data belongs to
0595: * @param field the binary field
0596: * @return An InputStream representing the binary data, <code>null</code> if no binary data was stored, or VALUE_SHORTED, if mayShorten
0597: */
0598: protected Blob getBlobFromDatabase(MMObjectNode node,
0599: CoreField field, boolean mayShorten) {
0600: try {
0601: MMObjectBuilder builder = node.getBuilder();
0602: Scheme scheme = factory.getScheme(Schemes.GET_BINARY_DATA,
0603: Schemes.GET_BINARY_DATA_DEFAULT);
0604: String query = scheme.format(this , builder, field, builder
0605: .getField("number"), node);
0606: getActiveConnection();
0607:
0608: PreparedStatement s = null;
0609: ResultSet result = null;
0610: try {
0611: s = activeConnection.prepareStatement(query);
0612: result = s.executeQuery();
0613: if ((result != null) && result.next()) {
0614: Blob blob = getBlobValue(result, 1, field,
0615: mayShorten);
0616: if (blob != null) {
0617: node.setSize(field.getName(), blob.length());
0618: }
0619: return blob;
0620: } else {
0621: if (result != null)
0622: result.close();
0623: s.close();
0624: throw new StorageException("Node with number "
0625: + node.getNumber() + " of type " + builder
0626: + " not found with query '" + query + "'");
0627: }
0628: } finally {
0629: if (result != null) {
0630: result.close();
0631: }
0632: if (s != null) {
0633: s.close();
0634: }
0635: }
0636: } catch (SQLException se) {
0637: throw new StorageException(se);
0638: } finally {
0639: releaseActiveConnection();
0640: }
0641: }
0642:
0643: // javadoc is inherited
0644: public byte[] getBinaryValue(MMObjectNode node, CoreField field)
0645: throws StorageException {
0646: try {
0647: Blob b = getBlobValue(node, field);
0648: if (b == null) {
0649: return null;
0650: } else {
0651: return b.getBytes(1, (int) b.length());
0652: }
0653: } catch (SQLException sqe) {
0654: throw new StorageException(sqe);
0655: }
0656: }
0657:
0658: // javadoc is inherited
0659: public InputStream getInputStreamValue(MMObjectNode node,
0660: CoreField field) throws StorageException {
0661: try {
0662: return getBlobValue(node, field).getBinaryStream();
0663: } catch (SQLException sqe) {
0664: throw new StorageException(sqe);
0665: }
0666: }
0667:
0668: public Blob getBlobValue(MMObjectNode node, CoreField field)
0669: throws StorageException {
0670: return getBlobValue(node, field, false);
0671: }
0672:
0673: public Blob getBlobValue(MMObjectNode node, CoreField field,
0674: boolean mayShorten) throws StorageException {
0675: if (checkStoreFieldAsFile(node.getBuilder())) {
0676: return getBlobFromFile(node, field, mayShorten);
0677: } else {
0678: return getBlobFromDatabase(node, field, mayShorten);
0679: }
0680: }
0681:
0682: /**
0683: * Retrieve a large binary object (byte array) for a specified object field.
0684: * The default method uses {@link ResultSet#getBytes(int)} to obtain text.
0685: * Override this method if you want to optimize retrieving large objects,
0686: * i.e by using clobs or streams.
0687: * @param result the resultset to retrieve the text from
0688: * @param index the index of the text in the resultset, or -1 to retireiv from file (blobs).
0689: * @param field the (MMBase) fieldtype. This value can be null
0690: * @return the retrieved data, <code>null</code> if no binary data was stored
0691: * @throws SQLException when a database error occurs
0692: * @throws StorageException when data is incompatible or the function is not supported
0693: */
0694: protected Blob getBlobValue(ResultSet result, int index,
0695: CoreField field, boolean mayShorten)
0696: throws StorageException, SQLException {
0697: if (factory.hasOption(Attributes.SUPPORTS_BLOB)) {
0698: Blob blob = result.getBlob(index);
0699: if (result.wasNull()) {
0700: return null;
0701: }
0702: if (mayShorten && shorten(field)) {
0703: return BLOB_SHORTED;
0704: }
0705:
0706: return blob;
0707: } else {
0708: try {
0709: InputStream inStream = result.getBinaryStream(index);
0710: if (result.wasNull()) {
0711: if (inStream != null) {
0712: try {
0713: inStream.close();
0714: } catch (RuntimeException e) {
0715: log.debug("" + e.getMessage(), e);
0716: }
0717: }
0718: return null;
0719: }
0720: if (mayShorten && shorten(field)) {
0721: if (inStream != null) {
0722: try {
0723: inStream.close();
0724: } catch (RuntimeException e) {
0725: log.debug("" + e.getMessage(), e);
0726: }
0727: }
0728: return BLOB_SHORTED;
0729: }
0730: return new InputStreamBlob(inStream);
0731: } catch (IOException ie) {
0732: throw new StorageException(ie);
0733: }
0734: }
0735: }
0736:
0737: /**
0738: * Defines how binary (blob) data files must look like.
0739: * @param node the node the binary data belongs to
0740: * @param fieldName the name of the binary field
0741: * @return The File where to store or read the binary data
0742: */
0743: protected File getBinaryFile(MMObjectNode node, String fieldName) {
0744: String basePath = factory.getBinaryFileBasePath();
0745: StringBuilder pathBuffer = new StringBuilder();
0746: int number = node.getNumber() / 1000;
0747: while (number > 0) {
0748: int num = number % 100;
0749: pathBuffer.insert(0, num);
0750: if (num < 10) {
0751: pathBuffer.insert(0, 0);
0752: }
0753: pathBuffer.insert(0, File.separator);
0754: number /= 100;
0755: }
0756:
0757: /*
0758: * This method is sometimes called with a node which has a supertype builder
0759: * attached instead of the real subtype builder. A read from the file system will fail,
0760: * because binaries are stored based on the subtype.
0761: */
0762: String builderName = null;
0763: int builderType = node.getBuilder().getObjectType();
0764: int realOtypeValue = node.getOType();
0765: if (builderType != realOtypeValue) {
0766: MMBase mmb = factory.getMMBase();
0767: builderName = mmb.getTypeDef().getValue(realOtypeValue);
0768: builderName = mmb.getBuilder(builderName)
0769: .getFullTableName();
0770: } else {
0771: builderName = node.getBuilder().getFullTableName();
0772: }
0773:
0774: pathBuffer.insert(0, basePath + factory.getDatabaseName()
0775: + File.separator + builderName);
0776: return new File(pathBuffer.toString(), "" + node.getNumber()
0777: + '.' + fieldName);
0778: }
0779:
0780: /**
0781: * Tries legacy paths
0782: * @returns such a File if found and readable, 'null' otherwise.
0783: */
0784: private File getLegacyBinaryFile(MMObjectNode node, String fieldName) {
0785: // the same basePath, so you so need to set that up right.
0786: String basePath = factory.getBinaryFileBasePath();
0787:
0788: File f = new File(basePath, node.getBuilder().getTableName()
0789: + File.separator + node.getNumber() + '.' + fieldName);
0790: if (f.exists()) { // 1.6 storage or 'support' blobdatadir
0791: if (!f.canRead()) {
0792: log.warn("Found '" + f + "' but it cannot be read");
0793: } else {
0794: return f;
0795: }
0796: }
0797:
0798: f = new File(basePath + File.separator + factory.getCatalog()
0799: + File.separator + node.getBuilder().getFullTableName()
0800: + File.separator + node.getNumber() + '.' + fieldName);
0801: if (f.exists()) { // 1.7.0.rc1 blob data dir
0802: if (!f.canRead()) {
0803: log.warn("Found '" + f + "' but it cannot be read");
0804: } else {
0805: return f;
0806: }
0807: }
0808:
0809: // don't know..
0810: return null;
0811:
0812: }
0813:
0814: /**
0815: * Check if binary data of this field should be stored in the database.
0816: * @param builder builder of this field
0817: * @return true if binary field should be stored as file, otherwise false.
0818: */
0819: private boolean checkStoreFieldAsFile(MMObjectBuilder builder) {
0820: if (factory.hasOption(Attributes.STORES_BINARY_AS_FILE)) {
0821: return true;
0822: } else if (factory.getStoreBinaryAsFileObjects().contains(
0823: builder.getTableName())) {
0824: return true;
0825: }
0826: return false;
0827: }
0828:
0829: /**
0830: * Store a binary (blob) data file
0831: * @todo how to do this in a transaction???
0832: * @param node the node the binary data belongs to
0833: * @param field the binary field
0834: */
0835: protected void storeBinaryAsFile(MMObjectNode node, CoreField field)
0836: throws StorageException {
0837: try {
0838: String fieldName = field.getName();
0839: File binaryFile = getBinaryFile(node, fieldName);
0840: binaryFile.getParentFile().mkdirs(); // make sure all directory exist.
0841: if (node.isNull(fieldName)) {
0842: if (field.isNotNull()) {
0843: node.storeValue(field.getName(),
0844: new ByteArrayInputStream(new byte[0]));
0845: } else {
0846: if (binaryFile.exists()) {
0847: binaryFile.delete();
0848: }
0849: return;
0850: }
0851: }
0852: long size = 0L;
0853: //log.warn("Storing " + field + " for " + node.getNumber());
0854: InputStream in = node.getInputStreamValue(fieldName);
0855: BufferedOutputStream out = new BufferedOutputStream(
0856: new FileOutputStream(binaryFile));
0857: byte[] buf = new byte[1024];
0858: int b = 0;
0859: while ((b = in.read(buf)) != -1) {
0860: size += b;
0861: out.write(buf, 0, b);
0862: }
0863: out.close();
0864: in.close();
0865: // unload the input-stream, it is of no use any more.
0866: node.setSize(fieldName, size);
0867: node.storeValue(fieldName, MMObjectNode.VALUE_SHORTED);
0868: } catch (IOException ie) {
0869: throw new StorageException(ie);
0870: }
0871: }
0872:
0873: /**
0874: * Checks whether file is readable and existing. Warns if not.
0875: * If non-existing it checks older locations.
0876: * @return the file to be used, or <code>null</code> if no existing readable file could be found, also no 'legacy' one.
0877: */
0878:
0879: protected File checkFile(File binaryFile, MMObjectNode node,
0880: CoreField field) {
0881: String fieldName = field.getName();
0882: if (!binaryFile.canRead()) {
0883: String desc = "while it should contain the byte array data for node '"
0884: + node.getNumber()
0885: + "' field '"
0886: + fieldName
0887: + "'. Returning null.";
0888: if (!binaryFile.exists()) {
0889: // try legacy
0890: File legacy = getLegacyBinaryFile(node, fieldName);
0891: if (legacy == null) {
0892: if (field.isNotNull()
0893: && !binaryFile.getParentFile().exists()) {
0894: log.warn("The file '" + binaryFile
0895: + "' does not exist, " + desc,
0896: new Exception());
0897: log
0898: .info("If you upgraded from older MMBase version, it might be that the blobs were stored on a different location. Make sure your blobs are in '"
0899: + factory
0900: .getBinaryFileBasePath()
0901: + "' (perhaps use symlinks?). If you changed configuration to 'blobs-on-disk' while it was blobs-in-database. Go to admin-pages.");
0902:
0903: } else if (log.isDebugEnabled()) {
0904: log
0905: .debug("The file '"
0906: + binaryFile
0907: + "' does not exist. Probably the blob field is simply 'null'");
0908: }
0909: } else {
0910: if (!legacyWarned) {
0911: log
0912: .warn("Using the legacy location '"
0913: + legacy
0914: + "' rather then '"
0915: + binaryFile
0916: + "'. You might want to convert this dir.");
0917: legacyWarned = true;
0918: }
0919: return legacy;
0920: }
0921: } else {
0922: log.error("The file '" + binaryFile
0923: + "' can not be read, " + desc);
0924: }
0925: return null;
0926: } else {
0927: return binaryFile;
0928: }
0929: }
0930:
0931: /**
0932: * Read a binary (blob) data file
0933: * @todo how to do this in a transaction???
0934: * @param node the node the binary data belongs to
0935: * @param field the binary field
0936: * @return the byte array containing the binary data, <code>null</code> if no binary data was stored
0937: */
0938: protected Blob getBlobFromFile(MMObjectNode node, CoreField field,
0939: boolean mayShorten) throws StorageException {
0940: String fieldName = field.getName();
0941: File binaryFile = checkFile(getBinaryFile(node, fieldName),
0942: node, field);
0943: if (binaryFile == null) {
0944: return null;
0945: }
0946: try {
0947: node.setSize(field.getName(), binaryFile.length());
0948: if (mayShorten && shorten(field)) {
0949: return BLOB_SHORTED;
0950: }
0951: return new InputStreamBlob(new FileInputStream(binaryFile),
0952: binaryFile.length());
0953: } catch (FileNotFoundException fnfe) {
0954: throw new StorageException(fnfe);
0955: }
0956: }
0957:
0958: // javadoc is inherited
0959: public int create(MMObjectNode node) throws StorageException {
0960: // assign a new number if the node has not yet been assigned one
0961: int nodeNumber = node.getNumber();
0962: if (nodeNumber == -1) {
0963: nodeNumber = createKey();
0964: node.setValue(MMObjectBuilder.FIELD_NUMBER, nodeNumber);
0965: }
0966: MMObjectBuilder builder = node.getBuilder();
0967: // precommit call, needed to convert or add things before a save
0968: // Should be done in MMObjectBuilder
0969: builder.preCommit(node);
0970: create(node, builder);
0971: commitChange(node, "n");
0972: unloadShortedFields(node, builder);
0973: //refresh(node);
0974: return nodeNumber;
0975: }
0976:
0977: /**
0978: * This method inserts a new object in a specific builder, and registers the change.
0979: * This method makes it easier to implement relational databases, where you may need to update the node
0980: * in more than one builder.
0981: * Call this method for all involved builders if you use a relational database.
0982: * @param node The node to insert. The node already needs to have a (new) number assigned
0983: * @param builder the builder to store the node
0984: * @throws StorageException if an error occurred during creation
0985: */
0986: protected void create(MMObjectNode node, MMObjectBuilder builder)
0987: throws StorageException {
0988: // get a builders fields
0989: List<CoreField> createFields = new ArrayList<CoreField>();
0990: List<CoreField> builderFields = builder
0991: .getFields(NodeManager.ORDER_CREATE);
0992: for (CoreField field : builderFields) {
0993: if (field.inStorage()) {
0994: createFields.add(field);
0995: }
0996: }
0997: String tablename = (String) factory
0998: .getStorageIdentifier(builder);
0999: create(node, createFields, tablename);
1000: }
1001:
1002: protected void create(MMObjectNode node,
1003: List<CoreField> createFields, String tablename) {
1004: // Create a String that represents the fields and values to be used in the insert.
1005: StringBuilder fieldNames = null;
1006: StringBuilder fieldValues = null;
1007:
1008: List<CoreField> fields = new ArrayList<CoreField>();
1009: for (CoreField field : createFields) {
1010: // skip bytevalues that are written to file
1011: if (checkStoreFieldAsFile(field.getParent())
1012: && (field.getType() == Field.TYPE_BINARY)) {
1013: storeBinaryAsFile(node, field);
1014: // do not handle this field further
1015: } else {
1016: // store the fieldname and the value parameter
1017: fields.add(field);
1018: String fieldName = (String) factory
1019: .getStorageIdentifier(field);
1020: if (fieldNames == null) {
1021: fieldNames = new StringBuilder(fieldName);
1022: fieldValues = new StringBuilder("?");
1023: } else {
1024: fieldNames.append(',').append(fieldName);
1025: fieldValues.append(",?");
1026: }
1027: }
1028: }
1029: if (log.isDebugEnabled()) {
1030: log.debug("insert field values " + fieldNames + " "
1031: + fieldValues);
1032: }
1033: if (fields.size() > 0) {
1034: Scheme scheme = factory.getScheme(Schemes.INSERT_NODE,
1035: Schemes.INSERT_NODE_DEFAULT);
1036: try {
1037: String query = scheme.format(this , tablename,
1038: fieldNames.toString(), fieldValues.toString());
1039: getActiveConnection();
1040: executeUpdateCheckConnection(query, node, fields);
1041: } catch (SQLException se) {
1042: throw new StorageException(se.getMessage()
1043: + " during creation of "
1044: + UNICODE_ESCAPER.transform(node.toString()),
1045: se);
1046: } finally {
1047: releaseActiveConnection();
1048: }
1049: }
1050: }
1051:
1052: protected void unloadShortedFields(MMObjectNode node,
1053: MMObjectBuilder builder) {
1054: for (CoreField field : builder.getFields()) {
1055: if (field.inStorage() && shorten(field)) {
1056: String fieldName = field.getName();
1057: if (!node.isNull(fieldName)) {
1058: node.storeValue(fieldName,
1059: MMObjectNode.VALUE_SHORTED);
1060: log.debug("Unloaded " + fieldName + " from node "
1061: + node.getNumber());
1062: }
1063: }
1064: }
1065: }
1066:
1067: /**
1068: * Executes an update query for given node and fields. It will close the connections which are no
1069: * good, which it determines by trying "SELECT 1 FROM <OBJECT TABLE>" after failure. If that happens, the connection
1070: * is explicitely closed (in case the driver has not done that), which will render is unusable
1071: * and at least GenericDataSource will automaticly try to get new ones.
1072: *
1073: * @throws SQLException If something wrong with the query, or the database is down or could not be contacted.
1074: * @since MMBase-1.7.1
1075: */
1076: protected void executeUpdateCheckConnection(String query,
1077: MMObjectNode node, List<CoreField> fields)
1078: throws SQLException {
1079: try {
1080: executeUpdate(query, node, fields);
1081: } catch (SQLException sqe) {
1082: while (true) {
1083: Statement s = null;
1084: ResultSet rs = null;
1085: try {
1086: s = activeConnection.createStatement();
1087: rs = s.executeQuery("SELECT 1 FROM "
1088: + factory.getMMBase().getBuilder("object")
1089: .getFullTableName()
1090: + " WHERE 1 = 0"); // if this goes wrong too it can't be the query
1091: } catch (SQLException isqe) {
1092: // so, connection must be broken.
1093: log.service("Found broken connection, closing it");
1094: if (activeConnection instanceof org.mmbase.module.database.MultiConnection) {
1095: ((org.mmbase.module.database.MultiConnection) activeConnection)
1096: .realclose();
1097: } else {
1098: activeConnection.close();
1099: }
1100: getActiveConnection();
1101: if (activeConnection.isClosed()) {
1102: // don't know if that can happen, but if it happens, this would perhaps avoid an infinite loop (and exception will get thrown in stead)
1103: break;
1104: }
1105: continue;
1106: } finally {
1107: if (s != null)
1108: s.close();
1109: if (rs != null)
1110: rs.close();
1111: }
1112: break;
1113: }
1114: executeUpdate(query, node, fields);
1115: }
1116: }
1117:
1118: /**
1119: * Executes an update query for given node and fields. This is wrapped in a function because it
1120: * is repeatedly called in {@link #executeUpdateCheckConnection} which in turn is called from
1121: * several spots in this class.
1122: *
1123: * @since MMBase-1.7.1
1124: */
1125: protected void executeUpdate(String query, MMObjectNode node,
1126: List<CoreField> fields) throws SQLException {
1127: PreparedStatement ps = activeConnection.prepareStatement(query);
1128: for (int fieldNumber = 0; fieldNumber < fields.size(); fieldNumber++) {
1129: CoreField field = fields.get(fieldNumber);
1130: try {
1131: setValue(ps, fieldNumber + 1, node, field);
1132: } catch (Exception e) {
1133: SQLException sqle = new SQLException(node.toString()
1134: + "/" + field + " " + e.getMessage());
1135: sqle.initCause(e);
1136: throw sqle;
1137: }
1138: }
1139: long startTime = getLogStartTime();
1140: ps.executeUpdate();
1141: ps.close();
1142: logQuery(query, startTime);
1143:
1144: }
1145:
1146: // javadoc is inherited
1147: public void change(MMObjectNode node) throws StorageException {
1148: // resolve aliases, if any.
1149: MMObjectBuilder builder = node.getBuilder();
1150: for (CoreField field : builder.getFields()) {
1151: if (field.getName().equals(MMObjectBuilder.FIELD_NUMBER))
1152: continue;
1153: if (field.getName().equals(
1154: MMObjectBuilder.FIELD_OBJECT_TYPE))
1155: continue;
1156: if (field.getType() == Field.TYPE_NODE) {
1157: Object value = node.getValue(field.getName());
1158: if (value instanceof String) {
1159: node.setValue(field.getName(), builder
1160: .getNode((String) value));
1161: }
1162: }
1163: }
1164: // precommit call, needed to convert or add things before a save
1165: // Should be done in MMObjectBuilder
1166: builder.preCommit(node);
1167: change(node, builder);
1168: commitChange(node, "c");
1169: unloadShortedFields(node, builder);
1170: // the node instance can be wrapped by other objects (org.mmbase.bridge.implementation.BasicNode) or otherwise still in use.
1171: // this make sure that the values are realistic reflections of the database:
1172: // This can change after a commit e.g. if the database enforces a maximum length for certain fields.
1173: refresh(node);
1174: }
1175:
1176: /**
1177: * Change this node in the specified builder.
1178: * This method makes it easier to implement relational databses, where you may need to update the node
1179: * in more than one builder.
1180: * Call this method for all involved builders if you use a relational database.
1181: * @param node The node to change
1182: * @param builder the builder to store the node
1183: * @throws StorageException if an error occurred during change
1184: */
1185: protected void change(MMObjectNode node, MMObjectBuilder builder)
1186: throws StorageException {
1187: List<CoreField> changeFields = new ArrayList<CoreField>();
1188: // obtain the node's changed fields
1189: Collection<String> fieldNames = node.getChanged();
1190: synchronized (fieldNames) { // make sure the set is not changed during this loop
1191: for (String key : fieldNames) {
1192: CoreField field = builder.getField(key);
1193: if ((field != null) && field.inStorage()) {
1194: changeFields.add(field);
1195: }
1196: }
1197: }
1198: String tablename = (String) factory
1199: .getStorageIdentifier(builder);
1200: change(node, builder, tablename, changeFields);
1201: }
1202:
1203: protected void change(MMObjectNode node, MMObjectBuilder builder,
1204: String tableName, Collection<CoreField> changeFields) {
1205: // Create a String that represents the fields to be used in the commit
1206: StringBuilder setFields = null;
1207: List<CoreField> fields = new ArrayList<CoreField>();
1208: for (CoreField field : changeFields) {
1209: // changing number is not allowed
1210: if ("number".equals(field.getName())
1211: || "otype".equals(field.getName())) {
1212: throw new StorageException("trying to change the '"
1213: + field.getName() + "' field of " + node
1214: + ". Changed fields " + node.getChanged());
1215: }
1216: // skip bytevalues that are written to file
1217: if (checkStoreFieldAsFile(field.getParent())
1218: && (field.getType() == Field.TYPE_BINARY)) {
1219: storeBinaryAsFile(node, field);
1220: } else {
1221: // handle this field - store it in fields
1222: fields.add(field);
1223: // store the fieldname and the value parameter
1224: String fieldName = (String) factory
1225: .getStorageIdentifier(field);
1226: if (setFields == null) {
1227: setFields = new StringBuilder(fieldName + "=?");
1228: } else {
1229: setFields.append(',').append(fieldName)
1230: .append("=?");
1231: }
1232: }
1233: }
1234: if (log.isDebugEnabled()) {
1235: log.debug("change field values " + node);
1236: }
1237: if (fields.size() > 0) {
1238: Scheme scheme = factory.getScheme(Schemes.UPDATE_NODE,
1239: Schemes.UPDATE_NODE_DEFAULT);
1240: try {
1241: String query = scheme.format(this , tableName, setFields
1242: .toString(), builder.getField("number"), node);
1243: getActiveConnection();
1244: executeUpdateCheckConnection(query, node, fields);
1245: } catch (SQLException se) {
1246: throw new StorageException(se.getMessage()
1247: + " for node " + node, se);
1248: } finally {
1249: releaseActiveConnection();
1250: }
1251: }
1252: }
1253:
1254: /**
1255: * Store the value of a field in a prepared statement
1256: * @todo Note that this code contains some code that should really be implemented in CoreField.
1257: * In particular, casting should be done in CoreField, IMO.
1258: * @param statement the prepared statement
1259: * @param index the index of the field in the prepared statement
1260: * @param node the node from which to retrieve the value
1261: * @param field the MMBase field, containing meta-information
1262: * @throws StorageException if the fieldtype is invalid, or data is invalid or missing
1263: * @throws SQLException if an error occurred while filling in the fields
1264: */
1265: protected void setValue(PreparedStatement statement, int index,
1266: MMObjectNode node, CoreField field)
1267: throws StorageException, SQLException {
1268: String fieldName = field.getName();
1269: Object value = node.getValue(fieldName);
1270: switch (field.getType()) {
1271: // Store numeric values
1272: case Field.TYPE_INTEGER:
1273: case Field.TYPE_FLOAT:
1274: case Field.TYPE_DOUBLE:
1275: case Field.TYPE_LONG:
1276: setNumericValue(statement, index, value, field, node);
1277: break;
1278: case Field.TYPE_BOOLEAN:
1279: setBooleanValue(statement, index, value, field, node);
1280: break;
1281: case Field.TYPE_DATETIME:
1282: setDateTimeValue(statement, index, value, field, node);
1283: break;
1284: // Store nodes
1285: case Field.TYPE_NODE:
1286: // cannot do getNodeValue here because that might cause a new connection to be needed -> deadlocks
1287: setNodeValue(statement, index, value, field, node);
1288: break;
1289: // Store strings
1290: case Field.TYPE_XML:
1291: setXMLValue(statement, index, value, field, node);
1292: break;
1293: case Field.TYPE_STRING:
1294: // note: do not use getStringValue, as this may attempt to
1295: // retrieve a (old, or nonexistent) value from the storage
1296: node.storeValue(fieldName, setStringValue(statement, index,
1297: value, field, node));
1298: break;
1299: // Store binary data
1300: case Field.TYPE_BINARY: {
1301: // note: do not use getByteValue, as this may attempt to
1302: // retrieve a (old, or nonexistent) value from the storage
1303: setBinaryValue(statement, index, value, field, node);
1304: break;
1305: }
1306: case Field.TYPE_LIST: {
1307: setListValue(statement, index, value, field, node);
1308: break;
1309: }
1310: default: // unknown field type - error
1311: throw new StorageException("unknown fieldtype");
1312: }
1313: }
1314:
1315: /**
1316: * Stores the 'null' value in the statement if appopriate (the value is null or unset, and the
1317: * value may indeed be NULL, according to the configuration). If the value is null or unset,
1318: * but the value may not be NULL, then -1 is stored.
1319: * @param statement the prepared statement
1320: * @param index the index of the field in the prepared statement
1321: * @param value the numeric value to store, which will be checked for null.
1322: * @param field the MMBase field, containing meta-information
1323: * @throws StorageException if the data is invalid or missing
1324: * @throws SQLException if an error occurred while filling in the fields
1325: * @return true if a null value was set, false otherwise
1326: * @since MMBase-1.7.1
1327: */
1328: protected boolean setNullValue(PreparedStatement statement,
1329: int index, Object value, CoreField field, int type)
1330: throws StorageException, SQLException {
1331: boolean mayBeNull = !field.isNotNull();
1332: if (value == null) { // value unset
1333: if (mayBeNull) {
1334: statement.setNull(index, type);
1335: return true;
1336: }
1337: /*
1338: } else if (value == MMObjectNode.VALUE_NULL) { // value explicitely set to 'null'
1339: if (mayBeNull) {
1340: statement.setNull(index, type);
1341: return true;
1342: } else {
1343: log.debug("Tried to set 'null' in field '" + field.getName() + "' but the field is 'NOT NULL', it will be cast.");
1344: }
1345: */
1346: }
1347:
1348: return false;
1349: }
1350:
1351: /**
1352: * Store a numeric value of a field in a prepared statement
1353: * The method uses the Casting class to convert to the appropriate value.
1354: * Null values are stored as NULL if possible, otherwise they are stored as -1.
1355: * Override this method if you want to override this behavior.
1356: * @param statement the prepared statement
1357: * @param index the index of the field in the prepared statement
1358: * @param value the numeric value to store. This may be a String, MMObjectNode, Numeric, or other value - the
1359: * method will convert it to the appropriate value.
1360: * @param field the MMBase field, containing meta-information
1361: * @param node the node that contains the data. Used to update this node if the database layer makes changes
1362: * to the data (i.e. creating a default value for a non-null field that had a null value)
1363: * @throws StorageException if the data is invalid or missing
1364: * @throws SQLException if an error occurred while filling in the fields
1365: */
1366: protected void setNumericValue(PreparedStatement statement,
1367: int index, Object value, CoreField field, MMObjectNode node)
1368: throws StorageException, SQLException {
1369: // Store integers, floats, doubles and longs
1370: if (!setNullValue(statement, index, value, field, field
1371: .getType())) {
1372: switch (field.getType()) { // it does this switch part twice now?
1373: case Field.TYPE_INTEGER: {
1374: int storeValue = Casting.toInt(value);
1375: statement.setInt(index, storeValue);
1376: node.storeValue(field.getName(), storeValue);
1377: break;
1378: }
1379: case Field.TYPE_FLOAT: {
1380: float storeValue = Casting.toFloat(value);
1381: statement.setFloat(index, storeValue);
1382: node.storeValue(field.getName(), storeValue);
1383: break;
1384: }
1385: case Field.TYPE_DOUBLE: {
1386: double storeValue = Casting.toDouble(value);
1387: statement.setDouble(index, storeValue);
1388: node.storeValue(field.getName(), storeValue);
1389: break;
1390: }
1391: case Field.TYPE_LONG: {
1392: long storeValue = Casting.toLong(value);
1393: statement.setLong(index, storeValue);
1394: node.storeValue(field.getName(), storeValue);
1395: break;
1396: }
1397: default:
1398: break;
1399: }
1400: }
1401: }
1402:
1403: /**
1404: * Store a node value of a field in a prepared statement
1405: * Nodes are stored in the database as numeric values.
1406: * Since a node value can be a (referential) key (depending on implementation),
1407: * Null values should be stored as NULL, not -1. If a field cannot be null when a
1408: * value is not given, an exception is thrown.
1409: * Override this method if you want to override this behavior.
1410: * @param statement the prepared statement
1411: * @param index the index of the field in the prepared statement
1412: * @param nodeValue the node to store
1413: * @param field the MMBase field, containing meta-information
1414: * @param node the node that contains the data.
1415: * @throws StorageException if the data is invalid or missing
1416: * @throws SQLException if an error occurred while filling in the fields
1417: */
1418: protected void setNodeValue(PreparedStatement statement, int index,
1419: Object nodeValue, CoreField field, MMObjectNode node)
1420: throws StorageException, SQLException {
1421: if (!setNullValue(statement, index, nodeValue, field,
1422: java.sql.Types.INTEGER)) {
1423: if (nodeValue == null && field.isNotNull()) {
1424: throw new StorageException("The NODE field with name "
1425: + field.getClass() + " " + field.getName()
1426: + " of type "
1427: + field.getParent().getTableName()
1428: + " can not be NULL.");
1429: }
1430: int nodeNumber;
1431: if (nodeValue instanceof MMObjectNode) {
1432: nodeNumber = ((MMObjectNode) nodeValue).getNumber();
1433: } else {
1434: nodeNumber = Casting.toInt(nodeValue);
1435: }
1436: if (nodeNumber < 0) {
1437: throw new StorageException("Node number " + nodeNumber
1438: + "(from " + nodeValue.getClass() + " "
1439: + nodeValue + ") is not valid for field '"
1440: + field.getName() + "' of node "
1441: + node.getNumber());
1442: }
1443: // retrieve node as a numeric value
1444: statement.setInt(index, nodeNumber);
1445: }
1446: }
1447:
1448: /**
1449: * Store a boolean value of a field in a prepared statement.
1450: * The method uses the Casting class to convert to the appropriate value.
1451: * Null values are stored as NULL if possible, otherwise they are stored as <code>false</code>
1452: * Override this method if you use another way to store booleans
1453: * @param statement the prepared statement
1454: * @param index the index of the field in the prepared statement
1455: * @param value the data (boolean) to store
1456: * @param field the MMBase field, containing meta-information
1457: * @param node the node that contains the data. Used to update this node if the database layer makes changes
1458: * to the data (i.e. creating a default value for a non-null field that had a null value)
1459: * @throws StorageException if the data is invalid or missing
1460: * @throws SQLException if an error occurred while filling in the fields
1461: * @since MMBase-1.8
1462: */
1463: protected void setBooleanValue(PreparedStatement statement,
1464: int index, Object value, CoreField field, MMObjectNode node)
1465: throws StorageException, SQLException {
1466: if (!setNullValue(statement, index, value, field,
1467: java.sql.Types.BOOLEAN)) {
1468: boolean bool = Casting.toBoolean(value);
1469: statement.setBoolean(index, bool);
1470: node.storeValue(field.getName(), Boolean.valueOf(bool));
1471: }
1472: }
1473:
1474: /**
1475: * Store a Date value of a field in a prepared statement.
1476: * The method uses the Casting class to convert to the appropriate value.
1477: * Null values are stored as NULL if possible, otherwise they are stored as the date 31/12/1969 23:59:59 GMT (-1)
1478: * TODO: I think that is -1000, not -1.
1479: *
1480: * Override this method if you use another way to store dates
1481: * @param statement the prepared statement
1482: * @param index the index of the field in the prepared statement
1483: * @param value the data (date) to store
1484: * @param field the MMBase field, containing meta-information
1485: * @param node the node that contains the data. Used to update this node if the database layer makes changes
1486: * to the data (i.e. creating a default value for a non-null field that had a null value)
1487: * @throws StorageException if the data is invalid or missing
1488: * @throws SQLException if an error occurred while filling in the fields
1489: * @since MMBase-1.8
1490: */
1491: protected void setDateTimeValue(PreparedStatement statement,
1492: int index, Object value, CoreField field, MMObjectNode node)
1493: throws StorageException, SQLException {
1494: if (!setNullValue(statement, index, value, field,
1495: java.sql.Types.TIMESTAMP)) {
1496: java.util.Date date = Casting.toDate(value);
1497: long time = date.getTime();
1498: // The driver will interpret the date object and convert it to the default timezone when storing.
1499:
1500: // undo that..
1501: if (log.isDebugEnabled()) {
1502: log.debug("Setting time " + date);
1503: log.debug("Converting with defaultTime Zone "
1504: + new java.util.Date(time
1505: - factory.getTimeZoneOffset(time)));
1506: log.debug("Offset with MMBase setting "
1507: + factory.getMMBase().getTimeZone().getOffset(
1508: time));
1509: }
1510: statement.setTimestamp(index, new Timestamp(time
1511: - factory.getTimeZoneOffset(time)));
1512: node.storeValue(field.getName(), date);
1513: }
1514: }
1515:
1516: /**
1517: * Store a List value of a field in a prepared statement.
1518: * The method uses the Casting class to convert to the appropriate value.
1519: * Null values are stored as NULL if possible, otherwise they are stored as an empty list.
1520: * Override this method if you use another way to store lists
1521: * @param statement the prepared statement
1522: * @param index the index of the field in the prepared statement
1523: * @param value the data (List) to store
1524: * @param field the MMBase field, containing meta-information. This value can be null
1525: * @param node the node that contains the data. Used to update this node if the database layer makes changes
1526: * to the data (i.e. creating a default value for a non-null field that had a null value)
1527: * @throws StorageException if the data is invalid or missing
1528: * @throws SQLException if an error occurred while filling in the fields
1529: * @since MMBase-1.8
1530: */
1531: protected void setListValue(PreparedStatement statement, int index,
1532: Object value, CoreField field, MMObjectNode node)
1533: throws StorageException, SQLException {
1534: if (!setNullValue(statement, index, value, field,
1535: java.sql.Types.ARRAY)) {
1536: List<?> list = Casting.toList(value);
1537: statement.setObject(index, list);
1538: node.storeValue(field.getName(), list);
1539: }
1540: }
1541:
1542: /**
1543: * Store binary data of a field in a prepared statement.
1544: * This basic implementation uses a binary stream to set the data.
1545: * Null values are stored as NULL if possible, otherwise they are stored as an empty byte-array.
1546: * Override this method if you use another way to store binaries (i.e. Blobs).
1547: * @param statement the prepared statement
1548: * @param index the index of the field in the prepared statement
1549: * @param objectValue the data (byte array) to store
1550: * @param field the MMBase field, containing meta-information
1551: * @param node the node that contains the data. Used to update this node if the database layer makes changes
1552: * to the data (i.e. creating a default value for a non-null field that had a null value)
1553: * @throws StorageException if the data is invalid or missing
1554: * @throws SQLException if an error occurred while filling in the fields
1555: */
1556: protected void setBinaryValue(PreparedStatement statement,
1557: int index, Object objectValue, CoreField field,
1558: MMObjectNode node) throws StorageException, SQLException {
1559: if (log.isDebugEnabled()) {
1560: log.debug("Setting inputstream bytes into field " + field);
1561: }
1562: if (!setNullValue(statement, index, objectValue, field,
1563: java.sql.Types.VARBINARY)) {
1564: log.debug("Didn't set null");
1565: InputStream stream = Casting.toInputStream(objectValue);
1566: long size = -1;
1567: if (objectValue instanceof byte[]) {
1568: size = ((byte[]) objectValue).length;
1569: } else {
1570: size = node.getSize(field.getName());
1571: }
1572: log.debug("Setting " + size + " bytes for inputstream");
1573: try {
1574: statement.setBinaryStream(index, stream, (int) size);
1575: stream.close();
1576: } catch (IOException ie) {
1577: throw new StorageException(ie);
1578: }
1579: }
1580: }
1581:
1582: /**
1583: * Store the text value of a field in a prepared statement.
1584: * Null values are stored as NULL if possible, otherwise they are stored as an empty string.
1585: * If the FORCE_ENCODE_TEXT option is set, text is encoded (using the MMBase encoding) to a byte array
1586: * and stored as a binary stream.
1587: * Otherwise it uses {@link PreparedStatement#setString(int, String)} to set the data.
1588: * Override this method if you use another way to store large texts (i.e. Clobs).
1589: * @param statement the prepared statement
1590: * @param index the index of the field in the prepared statement
1591: * @param objectValue the text to store
1592: * @param field the MMBase field, containing meta-information
1593: * @param node the node that contains the data.
1594: * @throws StorageException if the data is invalid or missing
1595: * @throws SQLException if an error occurred while filling in the fields
1596: */
1597: protected Object setStringValue(PreparedStatement statement,
1598: int index, Object objectValue, CoreField field,
1599: MMObjectNode node) throws StorageException, SQLException {
1600:
1601: if (setNullValue(statement, index, objectValue, field,
1602: java.sql.Types.VARCHAR))
1603: return objectValue;
1604: String value = Casting.toString(objectValue);
1605: if (factory.getSetSurrogator() != null) {
1606: value = factory.getSetSurrogator().transform(value);
1607: }
1608: String encoding = factory.getMMBase().getEncoding();
1609: // Store data as a binary stream when the code is a clob or blob, or
1610: // when database-force-encode-text is true.
1611: if (field.getStorageType() == Types.CLOB
1612: || field.getStorageType() == Types.BLOB
1613: || factory.hasOption(Attributes.FORCE_ENCODE_TEXT)) {
1614: byte[] rawchars = null;
1615: try {
1616: if (encoding.equalsIgnoreCase("ISO-8859-1")
1617: && factory.hasOption(Attributes.LIE_CP1252)) {
1618: encoding = "CP1252";
1619: } else {
1620: }
1621: rawchars = value.getBytes(encoding);
1622: ByteArrayInputStream stream = new ByteArrayInputStream(
1623: rawchars);
1624: statement.setBinaryStream(index, stream,
1625: rawchars.length);
1626: stream.close();
1627: } catch (IOException ie) {
1628: throw new StorageException(ie);
1629: }
1630: } else {
1631: String setValue = value;
1632: if (factory.hasOption(Attributes.LIE_CP1252)) {
1633: try {
1634: if (encoding.equalsIgnoreCase("ISO-8859-1")) {
1635: log.debug("Lying CP-1252");
1636: encoding = "CP1252";
1637: setValue = new String(value.getBytes("CP1252"),
1638: "ISO-8859-1");
1639: } else {
1640: }
1641: } catch (java.io.UnsupportedEncodingException uee) {
1642: // cannot happen
1643: }
1644: } else {
1645: }
1646: statement.setString(index, setValue);
1647:
1648: }
1649: if (value != null) {
1650: if (!encoding.equalsIgnoreCase("UTF-8")) {
1651: try {
1652: value = new String(value.getBytes(encoding),
1653: encoding);
1654: } catch (java.io.UnsupportedEncodingException uee) {
1655: log.error(uee);
1656: // cannot happen
1657: }
1658: }
1659:
1660: // execute also getSurrogator, to make sure that it does not confuse, and the node contains what it would contain if fetched from database.
1661: if (factory.getGetSurrogator() != null) {
1662: value = factory.getGetSurrogator().transform(value);
1663: }
1664: if (factory.hasOption(Attributes.TRIM_STRINGS)) {
1665: value = value.trim();
1666: }
1667: }
1668:
1669: if (objectValue == null)
1670: node.storeValue(field.getName(), value);
1671:
1672: return value;
1673: }
1674:
1675: /**
1676: * This default implementation calls {@link #setStringValue}.
1677: * Override this method if you want to override this behavior.
1678: * @since MMBase-1.7.1
1679: */
1680: protected void setXMLValue(PreparedStatement statement, int index,
1681: Object objectValue, CoreField field, MMObjectNode node)
1682: throws StorageException, SQLException {
1683: if (objectValue == null) {
1684: if (field.isNotNull()) {
1685: objectValue = "<p/>";
1686: }
1687: }
1688: objectValue = Casting.toXML(objectValue);
1689: if (objectValue != null) {
1690: objectValue = org.mmbase.util.xml.XMLWriter.write(
1691: (org.w3c.dom.Document) objectValue, false, true);
1692: }
1693: node.storeValue(field.getName(), objectValue);
1694: setStringValue(statement, index, objectValue, field, node);
1695: }
1696:
1697: // javadoc is inherited
1698: public void delete(MMObjectNode node) throws StorageException {
1699: // determine parent
1700: if (node.hasRelations()) {
1701: throw new StorageException("cannot delete node "
1702: + node.getNumber() + ", it still has relations");
1703: }
1704: delete(node, node.getBuilder());
1705: commitChange(node, "d");
1706: }
1707:
1708: /**
1709: * Delete a node from a specific builder
1710: * This method makes it easier to implement relational databses, where you may need to remove the node
1711: * in more than one builder.
1712: * Call this method for all involved builders if you use a relational database.
1713: * @param node The node to delete
1714: * @throws StorageException if an error occurred during delete
1715: */
1716: protected void delete(MMObjectNode node, MMObjectBuilder builder)
1717: throws StorageException {
1718: List<CoreField> blobFileField = new ArrayList<CoreField>();
1719: List<CoreField> builderFields = builder
1720: .getFields(NodeManager.ORDER_CREATE);
1721: for (CoreField field : builderFields) {
1722: if (field.inStorage()) {
1723: if (checkStoreFieldAsFile(builder)
1724: && (field.getType() == Field.TYPE_BINARY)) {
1725: blobFileField.add(field);
1726: }
1727: }
1728: }
1729: String tablename = (String) factory
1730: .getStorageIdentifier(builder);
1731: delete(node, builder, blobFileField, tablename);
1732: }
1733:
1734: protected void delete(MMObjectNode node, MMObjectBuilder builder,
1735: List<CoreField> blobFileField, String tablename) {
1736: try {
1737: Scheme scheme = factory.getScheme(Schemes.DELETE_NODE,
1738: Schemes.DELETE_NODE_DEFAULT);
1739: String query = scheme.format(this , tablename, builder
1740: .getField("number"), node);
1741: getActiveConnection();
1742: long startTime = getLogStartTime();
1743: PreparedStatement s = null;
1744: try {
1745: s = activeConnection.prepareStatement(query);
1746: s.executeUpdate();
1747: } finally {
1748: if (s != null) {
1749: s.close();
1750: }
1751: }
1752: logQuery(query, startTime);
1753:
1754: // delete blob files too
1755: for (CoreField field : blobFileField) {
1756: String fieldName = field.getName();
1757: File binaryFile = getBinaryFile(node, fieldName);
1758: File checkedFile = checkFile(binaryFile, node, field);
1759: if (checkedFile == null) {
1760: if (field.isNotNull()) {
1761: log
1762: .warn("Could not find blob for field to delete '"
1763: + fieldName
1764: + "' of node "
1765: + node.getNumber()
1766: + ": "
1767: + binaryFile);
1768: } else {
1769: // ok, value was probably simply 'null'.
1770: }
1771: } else if (!checkedFile.delete()) {
1772: log.warn("Could not delete '" + checkedFile + "'");
1773: } else {
1774: log.debug("Deleted '" + checkedFile + "'");
1775: }
1776: }
1777: } catch (SQLException se) {
1778: throw new StorageException(se);
1779: } finally {
1780: releaseActiveConnection();
1781: }
1782: }
1783:
1784: // javadoc is inherited
1785: public MMObjectNode getNode(final MMObjectBuilder builder,
1786: final int number) throws StorageException {
1787: if (builder == null)
1788: throw new IllegalArgumentException(
1789: "Builder cannot be null when requesting node "
1790: + number);
1791: Scheme scheme = factory.getScheme(Schemes.SELECT_NODE,
1792: Schemes.SELECT_NODE_DEFAULT);
1793: try {
1794: // create a new node (must be done before acquiring the connection, because this code might need a connection)
1795: MMObjectNode node = builder.getEmptyNode("system");
1796:
1797: getActiveConnection();
1798: // get a builders fields
1799: List<CoreField> builderFields = builder
1800: .getFields(NodeManager.ORDER_CREATE);
1801: StringBuilder fieldNames = null;
1802: for (CoreField field : builderFields) {
1803: if (field.inStorage()) {
1804: if (checkStoreFieldAsFile(builder)
1805: && (field.getType() == Field.TYPE_BINARY)) {
1806: continue;
1807: }
1808: if (field.getType() == Field.TYPE_BINARY) {
1809: continue;
1810: }
1811: // store the fieldname and the value parameter
1812: String fieldName = (String) factory
1813: .getStorageIdentifier(field);
1814: if (fieldNames == null) {
1815: fieldNames = new StringBuilder(fieldName);
1816: } else {
1817: fieldNames.append(',').append(fieldName);
1818: }
1819: }
1820: }
1821: String query = scheme.format(this , builder, fieldNames
1822: .toString(), builder.getField("number"), number);
1823: Statement s = activeConnection.createStatement();
1824: ResultSet result = null;
1825: try {
1826: result = s.executeQuery(query);
1827: fillNode(node, result, builder);
1828: } finally {
1829: if (result != null)
1830: result.close();
1831: s.close();
1832: }
1833: return node;
1834: } catch (SQLException se) {
1835: throw new StorageException(se.getClass().getName() + ": "
1836: + se.getMessage(), se);
1837: } finally {
1838: releaseActiveConnection();
1839: }
1840: }
1841:
1842: /**
1843: * Reloads the data from a node from the database.
1844: * Use this after a create or change action, so the data in memory is consistent with
1845: * any data stored in the database.
1846: * @param node the node to refresh
1847: */
1848: protected void refresh(MMObjectNode node) throws StorageException {
1849: Scheme scheme = factory.getScheme(Schemes.SELECT_NODE,
1850: Schemes.SELECT_NODE_DEFAULT);
1851: try {
1852: getActiveConnection();
1853: MMObjectBuilder builder = node.getBuilder();
1854: // get a builders fields
1855: List<CoreField> builderFields = builder
1856: .getFields(NodeManager.ORDER_CREATE);
1857: StringBuilder fieldNames = null;
1858: for (CoreField field : builderFields) {
1859: if (field.inStorage()) {
1860: if (checkStoreFieldAsFile(field.getParent())
1861: && (field.getType() == Field.TYPE_BINARY)) {
1862: continue;
1863: }
1864: // store the fieldname and the value parameter
1865: String fieldName = (String) factory
1866: .getStorageIdentifier(field);
1867: if (fieldNames == null) {
1868: fieldNames = new StringBuilder(fieldName);
1869: } else {
1870: fieldNames.append(',').append(fieldName);
1871: }
1872: }
1873: }
1874: String query = scheme.format(this , builder, fieldNames
1875: .toString(), builder.getField("number"), node
1876: .getNumber());
1877: Statement s = activeConnection.createStatement();
1878: ResultSet result = null;
1879: try {
1880: result = s.executeQuery(query);
1881: fillNode(node, result, builder);
1882: } finally {
1883: if (result != null)
1884: result.close();
1885: s.close();
1886: }
1887: } catch (SQLException se) {
1888: throw new StorageException(se);
1889: } finally {
1890: releaseActiveConnection();
1891: }
1892: }
1893:
1894: /**
1895: * Fills a single Node from the resultset of a query.
1896: * You can use this method to iterate through a query, creating multiple nodes, provided the resultset still contains
1897: * members (that is, <code>result.isAfterLast</code> returns <code>false</code>)
1898: * @param node The MMObjectNode to be filled
1899: * @param result the resultset
1900: * @param builder the builder to use for creating the node
1901: * @throws StorageException if the resultset is exhausted or a database error occurred
1902: */
1903: protected void fillNode(MMObjectNode node, ResultSet result,
1904: MMObjectBuilder builder) throws StorageException {
1905: try {
1906: if ((result != null) && result.next()) {
1907:
1908: // iterate through all a builder's fields, and retrieve the value for that field
1909: // Note that if we would do it the other way around (iterate through the recordset's fields)
1910: // we might get inconsistencies if we 'remap' fieldnames that need not be mapped.
1911: // this also guarantees the number field is set first, which we may need when retrieving blobs
1912: // from disk
1913: for (CoreField field : builder
1914: .getFields(NodeManager.ORDER_CREATE)) {
1915: if (field.inStorage()) {
1916: Object value;
1917: if (field.getType() == Field.TYPE_BINARY
1918: && checkStoreFieldAsFile(builder)) {
1919: value = getBlobFromFile(node, field, true);
1920: if (value == BLOB_SHORTED)
1921: value = MMObjectNode.VALUE_SHORTED;
1922: } else if (field.getType() == Field.TYPE_BINARY) {
1923: // it is never in the resultset that came from the database
1924: value = MMObjectNode.VALUE_SHORTED;
1925: } else {
1926: String id = (String) factory
1927: .getStorageIdentifier(field);
1928: value = getValue(result, result
1929: .findColumn(id), field, true);
1930: }
1931: if (value == null) {
1932: node.storeValue(field.getName(), null);
1933: } else {
1934: node.storeValue(field.getName(), value);
1935: }
1936: }
1937: }
1938: // clear the changed signal on the node
1939: node.clearChanged();
1940: return;
1941: } else {
1942: throw new StorageNotFoundException("Statement "
1943: + result.getStatement()
1944: + " (to fetch a Node) did not result anything");
1945: }
1946: } catch (SQLException se) {
1947: throw new StorageException(se);
1948: }
1949: }
1950:
1951: /**
1952: * Attempts to return a single field value from the resultset of a query.
1953: * @todo This method is called from the search query code and therefor needs to be public.
1954: * Perhaps code from searchquery should be moved to storage.
1955: * @param result the resultset
1956: * @param index the index of the field in the resultset
1957: * @param field the expected MMBase field type. This can be null
1958: * @param mayShorten Whether it would suffice to return only a 'shorted' version of the value.
1959: * @return the value
1960: * @throws StorageException if the value cannot be retrieved from the resultset
1961: */
1962:
1963: public Object getValue(ResultSet result, int index,
1964: CoreField field, boolean mayShorten)
1965: throws StorageException {
1966: try {
1967: int dbtype = Field.TYPE_UNKNOWN;
1968: if (field != null) {
1969: dbtype = field.getType();
1970: } else { // use database type.as
1971: dbtype = getJDBCtoField(result.getMetaData()
1972: .getColumnType(index), dbtype);
1973: }
1974:
1975: switch (dbtype) {
1976: // string-type fields
1977: case Field.TYPE_XML:
1978: return getXMLValue(result, index, field, mayShorten);
1979: case Field.TYPE_STRING:
1980: return getStringValue(result, index, field, mayShorten);
1981: case Field.TYPE_BINARY:
1982: Blob b = getBlobValue(result, index, field, mayShorten);
1983: if (b == BLOB_SHORTED)
1984: return MMObjectNode.VALUE_SHORTED;
1985: if (b == null)
1986: return null;
1987: return b.getBytes(1L, (int) b.length());
1988: case Field.TYPE_DATETIME:
1989: return getDateTimeValue(result, index, field);
1990: case Field.TYPE_BOOLEAN:
1991: return getBooleanValue(result, index, field);
1992: case Field.TYPE_INTEGER:
1993: case Field.TYPE_NODE:
1994: Object o = result.getObject(index);
1995: if (o instanceof Integer) {
1996: return o;
1997: } else if (o instanceof Number) {
1998: return Integer.valueOf(((Number) o).intValue());
1999: } else {
2000: return o;
2001: }
2002: default:
2003: return result.getObject(index);
2004: }
2005: } catch (SQLException se) {
2006: throw new StorageException(se);
2007: }
2008: }
2009:
2010: // javadoc is inherited
2011: public int getNodeType(int number) throws StorageException {
2012: Integer numberValue = number;
2013: Integer otypeValue = typeCache.get(numberValue);
2014: if (otypeValue != null) {
2015: return otypeValue.intValue();
2016: } else {
2017: Scheme scheme = factory.getScheme(Schemes.SELECT_NODE_TYPE,
2018: Schemes.SELECT_NODE_TYPE_DEFAULT);
2019: try {
2020: getActiveConnection();
2021: MMBase mmbase = factory.getMMBase();
2022: String query = scheme.format(this , mmbase, mmbase
2023: .getTypeDef().getField("number"), numberValue);
2024: Statement s = activeConnection.createStatement();
2025: long startTime = System.currentTimeMillis();
2026: try {
2027: ResultSet result = s.executeQuery(query);
2028: if (result != null) {
2029: try {
2030: if (result.next()) {
2031: int retval = result.getInt(1);
2032: typeCache.put(numberValue, retval);
2033: return retval;
2034: } else {
2035: return -1;
2036: }
2037: } finally {
2038: result.close();
2039: }
2040: } else {
2041: return -1;
2042: }
2043: } finally {
2044: logQuery(query, startTime);
2045: s.close();
2046: }
2047: } catch (SQLException se) {
2048: throw new StorageException(se);
2049: } finally {
2050: releaseActiveConnection();
2051: }
2052: }
2053: }
2054:
2055: /**
2056: * Returns whether tables inherit fields form parent tables.
2057: * this determines whether fields that are inherited in mmbase builders
2058: * are redefined in the database tables.
2059: */
2060: protected boolean tablesInheritFields() {
2061: return true;
2062: }
2063:
2064: /**
2065: * Determines whether the storage should make a field definition in a builder table for a
2066: * specified field.
2067: */
2068: protected boolean isPartOfBuilderDefinition(CoreField field) {
2069: // persistent field?
2070: // skip binary fields when values are written to file
2071: boolean isPart = field.inStorage()
2072: && (field.getType() != Field.TYPE_BINARY || !checkStoreFieldAsFile(field
2073: .getParent()));
2074: // also, if the database is OO, and the builder has a parent,
2075: // skip fields that are in the parent builder
2076: MMObjectBuilder parentBuilder = field.getParent()
2077: .getParentBuilder();
2078: if (isPart && parentBuilder != null) {
2079: isPart = !tablesInheritFields()
2080: || parentBuilder.getField(field.getName()) == null;
2081: }
2082: return isPart;
2083: }
2084:
2085: // javadoc is inherited
2086: public void create(MMObjectBuilder builder) throws StorageException {
2087: log.debug("Creating a table for " + builder);
2088: // use the builder to get the fields and create a
2089: // valid create SQL string
2090: // for backward compatibility, fields are to be created in the order defined
2091: List<CoreField> fields = builder
2092: .getFields(NodeManager.ORDER_CREATE);
2093: if (log.isDebugEnabled()) {
2094: log.debug("found fields " + fields);
2095: }
2096:
2097: List<CoreField> tableFields = new ArrayList<CoreField>();
2098: for (CoreField field : fields) {
2099: if (isPartOfBuilderDefinition(field)) {
2100: tableFields.add(field);
2101: }
2102: }
2103: String tableName = (String) factory
2104: .getStorageIdentifier(builder);
2105: createTable(builder, tableFields, tableName);
2106: if (!isVerified(builder)) {
2107: verify(builder);
2108: }
2109: }
2110:
2111: protected void createTable(MMObjectBuilder builder,
2112: List<CoreField> tableFields, String tableName) {
2113: StringBuilder createFields = new StringBuilder();
2114: StringBuilder createIndices = new StringBuilder();
2115: StringBuilder createFieldsAndIndices = new StringBuilder();
2116: StringBuilder createConstraints = new StringBuilder();
2117: // obtain the parentBuilder
2118: MMObjectBuilder parentBuilder = builder.getParentBuilder();
2119: Scheme rowtypeScheme;
2120: Scheme tableScheme;
2121: // if the builder has no parent, it is an object table,
2122: // so use CREATE_OBJECT_ROW_TYPE and CREATE_OBJECT_TABLE schemes.
2123: // Otherwise use CREATE_ROW_TYPE and CREATE_TABLE schemes.
2124: //
2125: if (parentBuilder == null) {
2126: rowtypeScheme = factory
2127: .getScheme(Schemes.CREATE_OBJECT_ROW_TYPE);
2128: tableScheme = factory.getScheme(
2129: Schemes.CREATE_OBJECT_TABLE,
2130: Schemes.CREATE_OBJECT_TABLE_DEFAULT);
2131: } else {
2132: rowtypeScheme = factory.getScheme(Schemes.CREATE_ROW_TYPE);
2133: tableScheme = factory.getScheme(Schemes.CREATE_TABLE,
2134: Schemes.CREATE_TABLE_DEFAULT);
2135: }
2136:
2137: for (CoreField field : tableFields) {
2138: try {
2139: // convert a fielddef to a field SQL createdefinition
2140: String fieldDef = getFieldDefinition(field);
2141: if (createFields.length() > 0) {
2142: createFields.append(", ");
2143: }
2144: createFields.append(fieldDef);
2145: // test on other indices
2146: String constraintDef = getConstraintDefinition(field);
2147: if (constraintDef != null) {
2148: // note: the indices are prefixed with a comma, as they generally follow the fieldlist.
2149: // if the database uses rowtypes, however, fields are not included in the CREATE TABLE statement,
2150: // and the comma should not be prefixed.
2151: if (rowtypeScheme == null
2152: || createIndices.length() > 0) {
2153: createIndices.append(", ");
2154: }
2155:
2156: createIndices.append(constraintDef);
2157: if (createFieldsAndIndices.length() > 0) {
2158: createFieldsAndIndices.append(", ");
2159: }
2160: createFieldsAndIndices.append(fieldDef + ", "
2161: + constraintDef);
2162: } else {
2163: if (createFieldsAndIndices.length() > 0) {
2164: createFieldsAndIndices.append(", ");
2165: }
2166: createFieldsAndIndices.append(fieldDef);
2167: }
2168: } catch (StorageException se) {
2169: // if something wrong with one field, don't fail the complete table.
2170: log.error("" + se.getMessage(), se);
2171: }
2172: }
2173: String query = "";
2174: try {
2175: getActiveConnection();
2176: // create a rowtype, if a scheme has been given
2177: // Note that creating a rowtype is optional
2178: if (rowtypeScheme != null) {
2179: query = rowtypeScheme.format(this , tableName,
2180: createFields.toString(), parentBuilder);
2181: // remove parenthesis with empty field definitions -
2182: // unfortunately Schems don't take this into account
2183: if (factory
2184: .hasOption(Attributes.REMOVE_EMPTY_DEFINITIONS)) {
2185: query = query.replaceAll("\\(\\s*\\)", "");
2186: }
2187: long startTime = getLogStartTime();
2188: PreparedStatement s = null;
2189: try {
2190: s = activeConnection.prepareStatement(query);
2191: s.executeUpdate();
2192: } finally {
2193: if (s != null) {
2194: s.close();
2195: }
2196: }
2197: logQuery(query, startTime);
2198: }
2199: // create the table
2200: query = tableScheme.format(this , tableName, createFields
2201: .toString(), createIndices.toString(),
2202: createFieldsAndIndices.toString(),
2203: createConstraints.toString(), parentBuilder,
2204: factory.getDatabaseName());
2205: // remove parenthesis with empty field definitions -
2206: // unfortunately Schemes don't take this into account
2207: if (factory.hasOption(Attributes.REMOVE_EMPTY_DEFINITIONS)) {
2208: query = query.replaceAll("\\(\\s*\\)", "");
2209: }
2210:
2211: PreparedStatement s = null;
2212: long startTime = getLogStartTime();
2213: try {
2214: s = activeConnection.prepareStatement(query);
2215: s.executeUpdate();
2216: } finally {
2217: if (s != null) {
2218: s.close();
2219: }
2220: }
2221: logQuery(query, startTime);
2222:
2223: addToTableNameCache(tableName);
2224:
2225: // create indices and unique constraints
2226: for (Index index : builder.getStorageConnector()
2227: .getIndices().values()) {
2228: create(index);
2229: }
2230:
2231: } catch (SQLException se) {
2232: throw new StorageException(se.getMessage() + " in query:"
2233: + query, se);
2234: } finally {
2235: releaseActiveConnection();
2236: }
2237: }
2238:
2239: protected void addToTableNameCache(String name) {
2240: tableNameCache.add(name.toUpperCase());
2241: }
2242:
2243: /**
2244: * @since MMBase-1.8.5
2245: */
2246: private long getMaxMaxSize(String name) {
2247: long maxMax = -1;
2248: for (TypeMapping tm : factory.getTypeMappings()) {
2249: if (name.equals(tm.name) && tm.maxSize > maxMax)
2250: maxMax = tm.maxSize;
2251: }
2252: return maxMax;
2253: }
2254:
2255: /**
2256: * Creates a field type definition, of the format '[fieldtype] NULL' or
2257: * '[fieldtype] NOT NULL' (depending on whether the field is nullable).
2258: * The fieldtype is taken from the type mapping in the factory.
2259: * @since MMBase-1.8
2260: * @param field the field
2261: * @return the typedefiniton as a String
2262: * @throws StorageException if the field type cannot be mapped
2263: */
2264: protected String getFieldTypeDefinition(CoreField field)
2265: throws StorageException {
2266: // create the type mapping to search for
2267: String typeName = Fields.getTypeDescription(field.getType());
2268: long size = field.getMaxLength();
2269: TypeMapping mapping = new TypeMapping();
2270: mapping.name = typeName;
2271: mapping.setFixedSize(size);
2272: // search type mapping
2273: List<TypeMapping> typeMappings = factory.getTypeMappings();
2274: int found = typeMappings.indexOf(mapping);
2275: if (found == -1) {
2276: long maxMax = getMaxMaxSize(typeName);
2277: if (size > maxMax) {
2278: mapping.setFixedSize(maxMax);
2279: found = typeMappings.indexOf(mapping);
2280: log.warn("Type for field " + field.getName() + ": "
2281: + typeName + " (" + size
2282: + ") undefined. Setting size to " + maxMax);
2283: size = maxMax;
2284: }
2285: }
2286: if (found > -1) {
2287: String fieldDef = typeMappings.get(found).getType(size);
2288: if (field.isNotNull()) {
2289: fieldDef += " NOT NULL";
2290: }
2291: return fieldDef;
2292: } else {
2293: throw new StorageException("Type for field "
2294: + field.getName() + ": " + typeName + " (" + size
2295: + ") undefined.");
2296: }
2297: }
2298:
2299: /**
2300: * Creates a fielddefinition, of the format '[fieldname] [fieldtype] NULL' or
2301: * '[fieldname] [fieldtype] NOT NULL' (depending on whether the field is nullable).
2302: * The fieldtype is taken from the type mapping in the factory.
2303: * @param field the field
2304: * @return the typedefiniton as a String
2305: * @throws StorageException if the field type cannot be mapped
2306: */
2307: protected String getFieldDefinition(CoreField field)
2308: throws StorageException {
2309: return factory.getStorageIdentifier(field) + " "
2310: + getFieldTypeDefinition(field);
2311: }
2312:
2313: /**
2314: * Creates an index definition string for a field to be passed when creating a table.
2315: * @param field the field for which to make the index definition
2316: * @return the index definition as a String, or <code>null</code> if no definition is available
2317: */
2318: protected String getConstraintDefinition(CoreField field)
2319: throws StorageException {
2320: String definitions = null;
2321: Scheme scheme = null;
2322: if (field.getName().equals("number")) {
2323: scheme = factory.getScheme(Schemes.CREATE_PRIMARY_KEY,
2324: Schemes.CREATE_PRIMARY_KEY_DEFAULT);
2325: if (scheme != null) {
2326: definitions = scheme.format(this , field.getParent(),
2327: field, factory.getMMBase());
2328: }
2329: } else {
2330: // the field is unique: create a unique key for it
2331: if (field.isUnique()) {
2332: scheme = factory.getScheme(Schemes.CREATE_UNIQUE_KEY,
2333: Schemes.CREATE_UNIQUE_KEY_DEFAULT);
2334: if (scheme != null) {
2335: definitions = scheme.format(this ,
2336: field.getParent(), field, field);
2337: }
2338: }
2339: if (field.getType() == Field.TYPE_NODE) {
2340: scheme = factory.getScheme(Schemes.CREATE_FOREIGN_KEY,
2341: Schemes.CREATE_FOREIGN_KEY_DEFAULT);
2342: if (scheme != null) {
2343: Object keyname = factory.getStorageIdentifier(""
2344: + field.getParent().getTableName() + "_"
2345: + field.getName() + "_FOREIGN");
2346: String definition = scheme.format(this , field
2347: .getParent(), field, factory.getMMBase(),
2348: factory.getStorageIdentifier("number"),
2349: keyname);
2350: if (definitions != null) {
2351: definitions += ", " + definition;
2352: } else {
2353: definitions = definition;
2354: }
2355: }
2356: }
2357: }
2358: return definitions;
2359: }
2360:
2361: // javadoc is inherited
2362: public void change(MMObjectBuilder builder) throws StorageException {
2363: // test if you can make changes
2364: // iterate through the fields,
2365: // use metadata.getColumns(...) to select fields
2366: // (incl. name, datatype, size, null)
2367: // use metadata.getImportedKeys(...) to get foreign keys
2368: // use metadata.getIndexInfo(...) to get composite and other indices
2369: // determine changes and run them
2370: throw new StorageException("Operation not supported");
2371: }
2372:
2373: // javadoc is inherited
2374: public synchronized void delete(MMObjectBuilder builder)
2375: throws StorageException {
2376: int size = size(builder);
2377: if (size != 0) {
2378: throw new StorageException(
2379: "Can not drop builder, it still contains " + size
2380: + " node(s)");
2381: }
2382: try {
2383: getActiveConnection();
2384: Scheme scheme = factory.getScheme(Schemes.DROP_TABLE,
2385: Schemes.DROP_TABLE_DEFAULT);
2386: String query = scheme.format(this , builder);
2387: Statement s = activeConnection.createStatement();
2388: long startTime = getLogStartTime();
2389: s.executeUpdate(query);
2390: s.close();
2391: logQuery(query, startTime);
2392: scheme = factory.getScheme(Schemes.DROP_ROW_TYPE);
2393: if (scheme != null) {
2394: query = scheme.format(this , builder);
2395: s = activeConnection.createStatement();
2396: long startTime2 = getLogStartTime();
2397: s.executeUpdate(query);
2398: s.close();
2399: logQuery(query, startTime2);
2400:
2401: String tableName = factory
2402: .getStorageIdentifier(builder).toString()
2403: .toUpperCase();
2404: if (tableNameCache.contains(tableName)) {
2405: tableNameCache.remove(tableName);
2406: }
2407: }
2408: } catch (Exception e) {
2409: throw new StorageException(e.getMessage());
2410: } finally {
2411: releaseActiveConnection();
2412: }
2413: }
2414:
2415: // javadoc is inherited
2416: public void create() throws StorageException {
2417: create(factory.getMMBase().getRootBuilder());
2418: createSequence();
2419: }
2420:
2421: /**
2422: * Creates a means for the database to pre-create keys with increasing numbers.
2423: * A sequence can be a database routine, a number table, or anything else that can be used to create unique numbers.
2424: * Keys can be obtained from the sequence by calling {@link #createKey()}.
2425: * @throws StorageException when the sequence can not be created
2426: */
2427: protected void createSequence() throws StorageException {
2428: synchronized (sequenceKeys) {
2429: try {
2430: getActiveConnection();
2431: // create the type mapping to search for
2432: String typeName = Fields
2433: .getTypeDescription(Field.TYPE_INTEGER);
2434: TypeMapping mapping = new TypeMapping();
2435: mapping.name = typeName;
2436: // search type mapping
2437: List<TypeMapping> typeMappings = factory
2438: .getTypeMappings();
2439: int found = typeMappings.indexOf(mapping);
2440: if (found == -1) {
2441: throw new StorageException("Type " + typeName
2442: + " undefined.");
2443: }
2444: String fieldName = (String) factory
2445: .getStorageIdentifier("number");
2446: String fieldDef = fieldName + " "
2447: + typeMappings.get(found).type
2448: + " NOT NULL, PRIMARY KEY(" + fieldName + ")";
2449: String query;
2450: Statement s;
2451: Scheme scheme = factory.getScheme(
2452: Schemes.CREATE_SEQUENCE,
2453: Schemes.CREATE_SEQUENCE_DEFAULT);
2454: if (scheme != null) {
2455: query = scheme.format(this , fieldDef, factory
2456: .getDatabaseName());
2457: long startTime = getLogStartTime();
2458: s = activeConnection.createStatement();
2459: s.executeUpdate(query);
2460: s.close();
2461: logQuery(query, startTime);
2462: }
2463: scheme = factory.getScheme(Schemes.INIT_SEQUENCE,
2464: Schemes.INIT_SEQUENCE_DEFAULT);
2465: if (scheme != null) {
2466: query = scheme.format(this , factory
2467: .getStorageIdentifier("number"), 1,
2468: bufferSize);
2469: long startTime = getLogStartTime();
2470: s = activeConnection.createStatement();
2471: s.executeUpdate(query);
2472: s.close();
2473: logQuery(query, startTime);
2474: }
2475: } catch (SQLException se) {
2476: throw new StorageException(se);
2477: } finally {
2478: releaseActiveConnection();
2479: }
2480: }
2481: }
2482:
2483: // javadoc is inherited
2484: public boolean exists(MMObjectBuilder builder)
2485: throws StorageException {
2486: boolean result = exists((String) factory
2487: .getStorageIdentifier(builder));
2488: if (result) {
2489: if (!isVerified(builder)) {
2490: verify(builder);
2491: }
2492: }
2493: return result;
2494: }
2495:
2496: /**
2497: * Queries the database metadata to test whether a given table exists.
2498: *
2499: * @param tableName name of the table to look for
2500: * @throws StorageException when the metadata could not be retrieved
2501: * @return <code>true</code> if the table exists
2502: */
2503: protected synchronized boolean exists(String tableName)
2504: throws StorageException {
2505: if (tableNameCache == null) {
2506: try {
2507: tableNameCache = new HashSet<String>();
2508: getActiveConnection();
2509: DatabaseMetaData metaData = activeConnection
2510: .getMetaData();
2511: String prefixTablename = factory.getMMBase()
2512: .getBaseName();
2513: if (metaData.storesLowerCaseIdentifiers()) {
2514: prefixTablename = prefixTablename.toLowerCase();
2515: }
2516: if (metaData.storesUpperCaseIdentifiers()) {
2517: prefixTablename = prefixTablename.toUpperCase();
2518: }
2519: ResultSet res = metaData.getTables(
2520: factory.getCatalog(), null, prefixTablename
2521: + "_%", new String[] { "TABLE", "VIEW",
2522: "SEQUENCE" });
2523: try {
2524: while (res.next()) {
2525: if (!tableNameCache.add(res.getString(3)
2526: .toUpperCase())) {
2527: log.warn("builder already in cache("
2528: + res.getString(3) + ")!");
2529: }
2530: }
2531: } finally {
2532: res.close();
2533: }
2534:
2535: } catch (Exception e) {
2536: throw new StorageException(e.getMessage());
2537: } finally {
2538: releaseActiveConnection();
2539: }
2540: }
2541:
2542: return tableNameCache.contains(tableName.toUpperCase());
2543: }
2544:
2545: // javadoc is inherited
2546: public boolean exists() throws StorageException {
2547: return exists(factory.getMMBase().getRootBuilder());
2548: }
2549:
2550: // javadoc is inherited
2551: public int size(MMObjectBuilder builder) throws StorageException {
2552: try {
2553: getActiveConnection();
2554: Scheme scheme = factory.getScheme(Schemes.GET_TABLE_SIZE,
2555: Schemes.GET_TABLE_SIZE_DEFAULT);
2556: String query = scheme.format(this , builder);
2557: Statement s = activeConnection.createStatement();
2558: ResultSet res = s.executeQuery(query);
2559: int retval;
2560: try {
2561: res.next();
2562: retval = res.getInt(1);
2563: } finally {
2564: res.close();
2565: }
2566: s.close();
2567: return retval;
2568: } catch (Exception e) {
2569: throw new StorageException(e);
2570: } finally {
2571: releaseActiveConnection();
2572: }
2573: }
2574:
2575: // javadoc is inherited
2576: public int size() throws StorageException {
2577: return size(factory.getMMBase().getRootBuilder());
2578: }
2579:
2580: /**
2581: * Guess the (mmbase) type in storage using the JDBC type.
2582: * Because a JDBC type can represent more than one mmbase Type,
2583: * the current type is also passed - if the current type matches, that type
2584: * is returned, otherwise the method returns the closest matching MMBase type.
2585: */
2586: protected int getJDBCtoField(int jdbcType, int mmbaseType) {
2587: switch (jdbcType) {
2588: case Types.INTEGER:
2589: case Types.SMALLINT:
2590: case Types.TINYINT:
2591: if (mmbaseType == Field.TYPE_INTEGER
2592: || mmbaseType == Field.TYPE_NODE) {
2593: return mmbaseType;
2594: } else {
2595: return Field.TYPE_INTEGER;
2596: }
2597: case Types.BIGINT:
2598: if (mmbaseType == Field.TYPE_INTEGER
2599: || mmbaseType == Field.TYPE_LONG
2600: || mmbaseType == Field.TYPE_NODE) {
2601: return mmbaseType;
2602: } else {
2603: return Field.TYPE_LONG;
2604: }
2605: case Types.FLOAT:
2606: case Types.REAL:
2607: return Field.TYPE_FLOAT;
2608: case Types.DOUBLE:
2609: case Types.NUMERIC:
2610: case Types.DECIMAL:
2611: if (mmbaseType == Field.TYPE_FLOAT
2612: || mmbaseType == Field.TYPE_DOUBLE) {
2613: return mmbaseType;
2614: } else {
2615: return Field.TYPE_DOUBLE;
2616: }
2617: case Types.BINARY:
2618: case Types.LONGVARBINARY:
2619: case Types.VARBINARY:
2620: case Types.BLOB:
2621: if (mmbaseType == Field.TYPE_BINARY
2622: || mmbaseType == Field.TYPE_STRING
2623: || mmbaseType == Field.TYPE_XML) {
2624: return mmbaseType;
2625: } else {
2626: return Field.TYPE_BINARY;
2627: }
2628: case Types.CHAR:
2629: case Types.CLOB:
2630: case Types.LONGVARCHAR:
2631: case Types.VARCHAR:
2632: if (mmbaseType == Field.TYPE_STRING
2633: || mmbaseType == Field.TYPE_XML) {
2634: return mmbaseType;
2635: } else {
2636: return Field.TYPE_STRING;
2637: }
2638: case Types.BIT:
2639: case Types.BOOLEAN:
2640: return Field.TYPE_BOOLEAN;
2641: case Types.DATE:
2642: case Types.TIME:
2643: case Types.TIMESTAMP:
2644: return Field.TYPE_DATETIME;
2645: case Types.ARRAY:
2646: return Field.TYPE_LIST;
2647: case Types.JAVA_OBJECT:
2648: case Types.OTHER:
2649: if (mmbaseType == Field.TYPE_LIST) {
2650: return mmbaseType;
2651: } else {
2652: return Field.TYPE_UNKNOWN;
2653: }
2654: default:
2655: return Field.TYPE_UNKNOWN;
2656: }
2657: }
2658:
2659: /**
2660: * Check if builders are already verified with the database.
2661: * @param builder Builder which might be verified
2662: * @return <code>true</code> when already verified
2663: */
2664: public boolean isVerified(MMObjectBuilder builder) {
2665: return verifiedTablesCache.contains(builder.getTableName()
2666: .toUpperCase());
2667: }
2668:
2669: /**
2670: * Tests whether a builder and the table present in the database match.
2671: */
2672: public void verify(MMObjectBuilder builder) throws StorageException {
2673: try {
2674: getActiveConnection();
2675: String tableName = (String) factory
2676: .getStorageIdentifier(builder);
2677: DatabaseMetaData metaData = activeConnection.getMetaData();
2678: if (metaData.storesUpperCaseIdentifiers()) {
2679: tableName = tableName.toUpperCase();
2680: }
2681: // skip if does not support inheritance, or if this is the object table
2682: if (tablesInheritFields()) {
2683: MMObjectBuilder parent = builder.getParentBuilder();
2684: try {
2685: ResultSet super TablesSet = metaData.getSuperTables(
2686: null, null, tableName);
2687: try {
2688: if (super TablesSet.next()) {
2689: String parentName = super TablesSet
2690: .getString("SUPERTABLE_NAME");
2691: if (parent == null
2692: || !parentName
2693: .equalsIgnoreCase((String) factory
2694: .getStorageIdentifier(parent))) {
2695: log
2696: .error("VERIFY: parent builder in storage for builder "
2697: + builder
2698: .getTableName()
2699: + " should be "
2700: + parent.getTableName()
2701: + " but defined as "
2702: + parentName);
2703: } else {
2704: log
2705: .debug("VERIFY: parent builder in storage for builder "
2706: + builder
2707: .getTableName()
2708: + " defined as "
2709: + parentName);
2710: }
2711: } else if (parent != null) {
2712: log
2713: .error("VERIFY: no parent builder defined in storage for builder "
2714: + builder.getTableName());
2715: }
2716: } finally {
2717: super TablesSet.close();
2718: }
2719: } catch (AbstractMethodError ae) {
2720: // ignore: the method is not implemented by the JDBC Driver
2721: log
2722: .debug("VERIFY: Driver does not fully implement the JDBC 3.0 API, skipping inheritance consistency tests for "
2723: + tableName);
2724: } catch (UnsupportedOperationException uoe) {
2725: // ignore: the operation is not supported by the JDBC Driver
2726: log
2727: .debug("VERIFY: Driver does not support all JDBC 3.0 methods, skipping inheritance consistency tests for "
2728: + tableName);
2729: } catch (SQLException se) {
2730: // ignore: the method is likely not implemented by the JDBC Driver
2731: // (should be one of the above errors, but postgresql returns this as an SQLException. Tsk.)
2732: log
2733: .debug("VERIFY: determining super tables failed, skipping inheritance consistency tests for "
2734: + tableName);
2735: }
2736: }
2737: Map<String, Map<String, Object>> columns = new HashMap<String, Map<String, Object>>();
2738: ResultSet columnsSet = metaData.getColumns(null, null,
2739: tableName, null);
2740: try {
2741: // get column information
2742: while (columnsSet.next()) {
2743: Map<String, Object> colInfo = new HashMap<String, Object>();
2744: colInfo.put("DATA_TYPE", columnsSet
2745: .getInt("DATA_TYPE"));
2746: colInfo.put("TYPE_NAME", columnsSet
2747: .getString("TYPE_NAME"));
2748: colInfo.put("COLUMN_SIZE", columnsSet
2749: .getInt("COLUMN_SIZE"));
2750: colInfo
2751: .put(
2752: "NULLABLE",
2753: Boolean
2754: .valueOf(columnsSet
2755: .getInt("NULLABLE") != DatabaseMetaData.columnNoNulls));
2756: columns.put(columnsSet.getString("COLUMN_NAME"),
2757: colInfo);
2758: }
2759: } finally {
2760: columnsSet.close();
2761: }
2762: // iterate through fields and check all fields present
2763: int pos = 0;
2764: List<CoreField> builderFields = builder
2765: .getFields(NodeManager.ORDER_CREATE);
2766: for (CoreField field : builderFields) {
2767: if (field.inStorage()
2768: && (field.getType() != Field.TYPE_BINARY || !checkStoreFieldAsFile(field
2769: .getParent()))) {
2770: field.rewrite();
2771: pos++;
2772: Object id = field.getStorageIdentifier(); // why the fuck is this an Object and not a String
2773: Map<String, Object> colInfo = columns.get(id);
2774: if (colInfo == null) {
2775: colInfo = columns.get(("" + id).toLowerCase());
2776: }
2777: if (colInfo == null) {
2778:
2779: log
2780: .error("VERIFY: Field '"
2781: + field.getName()
2782: + "' "
2783: + (id.equals(field.getName()) ? ""
2784: : "(mapped to field '"
2785: + id + "') ")
2786: + "of builder '"
2787: + builder.getTableName()
2788: + "' does NOT exist in storage! Field will be considered virtual");
2789:
2790: // set field to virtual so it will not be stored -
2791: // prevents future queries or statements from failing
2792: field.setState(Field.STATE_VIRTUAL);
2793: } else {
2794: // compare type
2795: int curtype = field.getType();
2796: int storageType = (Integer) colInfo
2797: .get("DATA_TYPE");
2798: field.setStorageType(storageType);
2799: int type = getJDBCtoField(storageType, curtype);
2800: if (type != curtype) {
2801: log
2802: .warn("VERIFY: Field '"
2803: + field.getName()
2804: + "' of builder '"
2805: + builder.getTableName()
2806: + "' mismatch : type defined as "
2807: + Fields
2808: .getTypeDescription(curtype)
2809: + ", but in storage "
2810: + Fields
2811: .getTypeDescription(type)
2812: + " ("
2813: + colInfo.get("TYPE_NAME")
2814: + "). Storage type will be used.");
2815: // set the new type (keep the old datatype)
2816: if (type == Field.TYPE_UNKNOWN) {
2817: log
2818: .warn("Storage type = 'UNKNOWN', wil not fall back to _that_");
2819: } else {
2820: field.setType(type);
2821: }
2822: }
2823: boolean nullable = (Boolean) colInfo
2824: .get("NULLABLE");
2825: if (nullable == field.isNotNull()) {
2826: // only correct if storage is more restrictive
2827: if (!nullable) {
2828: field.setNotNull(!nullable);
2829: log
2830: .warn("VERIFY: Field '"
2831: + field.getName()
2832: + "' of builder '"
2833: + builder
2834: .getTableName()
2835: + "' mismatch : notnull in storage is "
2836: + !nullable
2837: + " (value corrected for this session)");
2838: } else {
2839: log
2840: .debug("VERIFY: Field '"
2841: + field.getName()
2842: + "' of builder '"
2843: + builder
2844: .getTableName()
2845: + "' mismatch : notnull in storage is "
2846: + !nullable);
2847: }
2848: }
2849: // compare size
2850: int size = (Integer) colInfo.get("COLUMN_SIZE");
2851: int cursize = field.getMaxLength();
2852: // ignore the size difference for large fields (generally blobs or memo texts)
2853: // since most databases do not return accurate sizes for these fields
2854: if (cursize != -1 && size > 0
2855: && size != cursize && cursize <= 255) {
2856: if (size < cursize) {
2857: // only correct if storage is more restrictive
2858: field.setMaxLength(size);
2859: log
2860: .warn("VERIFY: Field '"
2861: + field.getName()
2862: + "' of builder '"
2863: + builder
2864: .getTableName()
2865: + "' mismatch : size defined as "
2866: + cursize
2867: + ", but in storage "
2868: + size
2869: + " (value corrected for this session)");
2870: } else {
2871: log
2872: .debug("VERIFY: Field '"
2873: + field.getName()
2874: + "' of builder '"
2875: + builder
2876: .getTableName()
2877: + "' mismatch : size defined as "
2878: + cursize
2879: + ", but in storage "
2880: + size);
2881: }
2882: }
2883: columns.remove(id);
2884: }
2885: // lock the field now that it has been checked
2886: // this prevents any accidental changes to the field.
2887: field.finish();
2888: }
2889: }
2890: // if any are left, these fields were removed!
2891: for (String column : columns.keySet()) {
2892: log.warn("VERIFY: Column '" + column
2893: + "' for builder '" + builder.getTableName()
2894: + "' in Storage but not defined!");
2895: }
2896: } catch (Exception e) {
2897: log.error(
2898: "Error during check of table (Assume table is correct.):"
2899: + e.getMessage(), e);
2900: } finally {
2901: releaseActiveConnection();
2902: }
2903: verifiedTablesCache.add(builder.getTableName().toUpperCase());
2904: }
2905:
2906: /**
2907: * Determines if an index exists.
2908: * You should have an active connection before calling this method.
2909: * @param index the index to test
2910: * @param tablename the tablename to test the index against
2911: * @throws StorageException when a database error occurs
2912: */
2913: protected boolean exists(Index index, String tablename) {
2914: boolean result = false;
2915: try {
2916: DatabaseMetaData metaData = activeConnection.getMetaData();
2917: ResultSet indexSet = metaData.getIndexInfo(null, null,
2918: tablename, index.isUnique(), false);
2919: try {
2920: String indexName = (String) factory
2921: .getStorageIdentifier(index);
2922: while (!result && indexSet.next()) {
2923: int indexType = indexSet.getInt("TYPE");
2924: if (indexType != DatabaseMetaData.tableIndexStatistic) {
2925: result = indexName.equalsIgnoreCase(indexSet
2926: .getString("INDEX_NAME"));
2927: }
2928: }
2929: } finally {
2930: indexSet.close();
2931: }
2932: } catch (SQLException se) {
2933: throw new StorageException(se);
2934: }
2935: return result;
2936: }
2937:
2938: /**
2939: * Determines if an index exists.
2940: * You should have an active connection before calling this method.
2941: * @param index the index to test
2942: * @throws StorageException when a database error occurs
2943: */
2944: protected boolean exists(Index index) throws StorageException {
2945: return exists(index, index.getParent().getTableName());
2946: }
2947:
2948: /**
2949: * Drop all constraints and indices that contain a specific field.
2950: * You should have an active connection before calling this method.
2951: * @param field the field for which to drop indices
2952: * @throws StorageException when a database error occurs
2953: */
2954: protected void deleteIndices(CoreField field)
2955: throws StorageException {
2956: for (Object element : field.getParent().getStorageConnector()
2957: .getIndices().values()) {
2958: Index index = (Index) element;
2959: if (index.contains(field)) {
2960: delete(index);
2961: }
2962: }
2963: }
2964:
2965: /**
2966: * Drop a constraint or index.
2967: * You should have an active connection before calling this method.
2968: * @param index the index to drop
2969: * @throws StorageException when a database error occurs
2970: */
2971: protected void delete(Index index) throws StorageException {
2972: Scheme deleteIndexScheme;
2973: if (index.isUnique()) {
2974: // Scheme: DELETE_CONSTRAINT
2975: deleteIndexScheme = factory.getScheme(
2976: Schemes.DELETE_UNIQUE_INDEX,
2977: Schemes.DELETE_UNIQUE_INDEX_DEFAULT);
2978: } else {
2979: // Scheme: DELETE_INDEX
2980: deleteIndexScheme = factory.getScheme(Schemes.DELETE_INDEX,
2981: Schemes.DELETE_INDEX_DEFAULT);
2982: }
2983: if (deleteIndexScheme != null && exists(index)) {
2984: // remove index
2985: String query = null;
2986: try {
2987: Statement s = activeConnection.createStatement();
2988: query = deleteIndexScheme.format(this , index
2989: .getParent(), index);
2990: long startTime = getLogStartTime();
2991: try {
2992: s.executeUpdate(query);
2993: } finally {
2994: s.close();
2995: }
2996: logQuery(query, startTime);
2997: } catch (SQLException se) {
2998: throw new StorageException(se.getMessage()
2999: + " in query:" + query, se);
3000: }
3001: }
3002: }
3003:
3004: /**
3005: * Returns a comma seperated list of fieldnames for an index.
3006: * @param index the index to create it for
3007: * @return the field list definition as a String, or <code>null</code> if the index was empty, or
3008: * if it consists of a composite index and composite indices are not supported.
3009: */
3010: protected String getFieldList(Index index) {
3011: String result = null;
3012: if (index.size() == 1
3013: || factory
3014: .hasOption(Attributes.SUPPORTS_COMPOSITE_INDEX)) {
3015: StringBuilder indexFields = new StringBuilder();
3016: for (Field field : index) {
3017: if (indexFields.length() > 0) {
3018: indexFields.append(", ");
3019: }
3020: indexFields.append(factory.getStorageIdentifier(field));
3021: }
3022: if (indexFields.length() > 0) {
3023: result = indexFields.toString();
3024: }
3025: }
3026: return result;
3027: }
3028:
3029: /**
3030: * (Re)create all constraints and indices that contain a specific field.
3031: * You should have an active connection before calling this method.
3032: * @param field the field for which to create indices
3033: * @throws StorageException when a database error occurs
3034: */
3035: protected void createIndices(CoreField field)
3036: throws StorageException {
3037: for (Object element : field.getParent().getStorageConnector()
3038: .getIndices().values()) {
3039: Index index = (Index) element;
3040: if (index.contains(field)) {
3041: create(index);
3042: }
3043: }
3044: }
3045:
3046: /**
3047: * Create an index or a unique constraint.
3048: * @param index the index to create
3049: */
3050: protected void create(Index index) throws StorageException {
3051: String tablename = (String) factory.getStorageIdentifier(index
3052: .getParent());
3053: createIndex(index, tablename);
3054: }
3055:
3056: /**
3057: * Create an index or a unique constraint.
3058: * @param index the index to create
3059: * @param tablename name of the table
3060: */
3061: protected void createIndex(Index index, String tablename) {
3062: Scheme createIndexScheme;
3063: if (index.isUnique()) {
3064: // Scheme: CREATE_UNIQUE_INDEX
3065: createIndexScheme = factory.getScheme(
3066: Schemes.CREATE_UNIQUE_INDEX,
3067: Schemes.CREATE_UNIQUE_INDEX_DEFAULT);
3068: } else {
3069: // Scheme: CREATE_INDEX
3070: createIndexScheme = factory.getScheme(Schemes.CREATE_INDEX,
3071: Schemes.CREATE_INDEX_DEFAULT);
3072: }
3073: // note: do not attempt to create an index if it already exists.
3074: if (createIndexScheme != null && !exists(index, tablename)) {
3075: String fieldlist = getFieldList(index);
3076: if (fieldlist != null) {
3077: String query = null;
3078: try {
3079: Statement s = activeConnection.createStatement();
3080: query = createIndexScheme.format(this , tablename,
3081: fieldlist, index);
3082: long startTime = getLogStartTime();
3083: try {
3084: s.executeUpdate(query);
3085: } finally {
3086: s.close();
3087: }
3088: logQuery(query, startTime);
3089: } catch (SQLException se) {
3090: throw new StorageException(se.getMessage()
3091: + " in query:" + query, se);
3092: }
3093: }
3094: }
3095: }
3096:
3097: // javadoc is inherited
3098: public void create(CoreField field) throws StorageException {
3099: if (field == null)
3100: throw new IllegalArgumentException("No field given");
3101: if (!factory.hasOption(Attributes.SUPPORTS_DATA_DEFINITION)) {
3102: throw new StorageException(
3103: "Data definiton statements (create new field) are not supported.");
3104: }
3105: if (factory.getScheme(Schemes.CREATE_OBJECT_ROW_TYPE) != null) {
3106: throw new StorageException(
3107: "Can not use data definiton statements (create new field) on row types.");
3108: }
3109: log.debug("Creating new field " + field);
3110: if (field.inStorage()
3111: && (field.getType() != Field.TYPE_BINARY || !checkStoreFieldAsFile(field
3112: .getParent()))) {
3113: Scheme scheme = factory.getScheme(Schemes.CREATE_FIELD,
3114: Schemes.CREATE_FIELD_DEFAULT);
3115: if (scheme == null) {
3116: throw new StorageException(
3117: "Storage layer does not support the dynamic creation of fields");
3118: } else {
3119: try {
3120: getActiveConnection();
3121: // add field
3122: String fieldTypeDef = getFieldTypeDefinition(field);
3123: String query = scheme.format(this , field
3124: .getParent(), field, fieldTypeDef);
3125: Statement s = activeConnection.createStatement();
3126: long startTime = getLogStartTime();
3127: s.executeUpdate(query);
3128: s.close();
3129: logQuery(query, startTime);
3130: // add constraints
3131: String constraintDef = getConstraintDefinition(field);
3132: if (constraintDef != null) {
3133: scheme = factory.getScheme(
3134: Schemes.CREATE_CONSTRAINT,
3135: Schemes.CREATE_CONSTRAINT_DEFAULT);
3136: if (scheme != null) {
3137: query = scheme.format(this , field
3138: .getParent(), constraintDef);
3139: s = activeConnection.createStatement();
3140: s.executeUpdate(query);
3141: s.close();
3142: logQuery(query, startTime);
3143: }
3144: }
3145: deleteIndices(field);
3146: createIndices(field);
3147: } catch (SQLException se) {
3148: throw new StorageException(se);
3149: } finally {
3150: releaseActiveConnection();
3151: }
3152: }
3153: }
3154: }
3155:
3156: // javadoc is inherited
3157: public void change(CoreField field) throws StorageException {
3158: if (!factory.hasOption(Attributes.SUPPORTS_DATA_DEFINITION)) {
3159: throw new StorageException(
3160: "Data definiton statements (change field) are not supported.");
3161: }
3162: if (factory.getScheme(Schemes.CREATE_OBJECT_ROW_TYPE) != null) {
3163: throw new StorageException(
3164: "Can not use data definiton statements (change field) on row types.");
3165: }
3166: if (field.inStorage()
3167: && (field.getType() != Field.TYPE_BINARY || !checkStoreFieldAsFile(field
3168: .getParent()))) {
3169: Scheme scheme = factory.getScheme(Schemes.CHANGE_FIELD,
3170: Schemes.CHANGE_FIELD_DEFAULT);
3171: if (scheme == null) {
3172: throw new StorageException(
3173: "Storage layer does not support the dynamic changing of fields");
3174: } else {
3175: try {
3176: getActiveConnection();
3177: deleteIndices(field);
3178: String fieldTypeDef = getFieldTypeDefinition(field);
3179: String query = scheme.format(this , field
3180: .getParent(), field, fieldTypeDef);
3181: Statement s = activeConnection.createStatement();
3182: long startTime = getLogStartTime();
3183: s.executeUpdate(query);
3184: s.close();
3185: logQuery(query, startTime);
3186: // add constraints
3187: String constraintDef = getConstraintDefinition(field);
3188: if (constraintDef != null) {
3189: scheme = factory.getScheme(
3190: Schemes.CREATE_CONSTRAINT,
3191: Schemes.CREATE_CONSTRAINT_DEFAULT);
3192: if (scheme != null) {
3193: query = scheme.format(this , field
3194: .getParent(), constraintDef);
3195: s = activeConnection.createStatement();
3196: long startTime2 = getLogStartTime();
3197: s.executeUpdate(query);
3198: s.close();
3199: logQuery(query, startTime2);
3200: }
3201: }
3202: createIndices(field);
3203: } catch (SQLException se) {
3204: throw new StorageException(se);
3205: } finally {
3206: releaseActiveConnection();
3207: }
3208: }
3209: }
3210: }
3211:
3212: // javadoc is inherited
3213: public void delete(CoreField field) throws StorageException {
3214: if (!factory.hasOption(Attributes.SUPPORTS_DATA_DEFINITION)) {
3215: throw new StorageException(
3216: "Data definiton statements (delete field) are not supported.");
3217: }
3218: if (factory.getScheme(Schemes.CREATE_OBJECT_ROW_TYPE) != null) {
3219: throw new StorageException(
3220: "Can not use data definiton statements (delete field) on row types.");
3221: }
3222: if (field.inStorage()
3223: && (field.getType() != Field.TYPE_BINARY || !checkStoreFieldAsFile(field
3224: .getParent()))) {
3225: Scheme scheme = factory.getScheme(Schemes.DELETE_FIELD,
3226: Schemes.DELETE_FIELD_DEFAULT);
3227: if (scheme == null) {
3228: throw new StorageException(
3229: "Storage layer does not support the dynamic deleting of fields");
3230: } else {
3231: try {
3232: getActiveConnection();
3233: deleteIndices(field);
3234: String query = scheme.format(this , field
3235: .getParent(), field);
3236: Statement s = activeConnection.createStatement();
3237: long startTime = getLogStartTime();
3238: s.executeUpdate(query);
3239: s.close();
3240: logQuery(query, startTime);
3241: createIndices(field);
3242: } catch (SQLException se) {
3243: throw new StorageException(se);
3244: } finally {
3245: releaseActiveConnection();
3246: }
3247: }
3248: }
3249: }
3250:
3251: /**
3252: * Convert legacy file
3253: * @return Number of converted fields. Or -1 if not storing binaries as files
3254: */
3255: public int convertLegacyBinaryFiles()
3256: throws org.mmbase.storage.search.SearchQueryException,
3257: SQLException {
3258: if (factory.hasOption(Attributes.STORES_BINARY_AS_FILE)) {
3259: synchronized (factory) { // there is only on factory. This makes sure that there is only one conversion running
3260: int result = 0;
3261: int fromDatabase = 0;
3262: for (MMObjectBuilder builder : factory.getMMBase()
3263: .getBuilders()) {
3264: ;
3265: // remove clusternodes from the convert
3266: if (!builder.getSingularName().equals(
3267: "clusternodes")) {
3268: for (CoreField field : builder.getFields()) {
3269: String fieldName = field.getName();
3270: if (field.getType() == Field.TYPE_BINARY) { // check all binaries
3271: // check whether it might be in a column
3272: boolean foundColumn = false;
3273: try {
3274: getActiveConnection();
3275: String tableName = (String) factory
3276: .getStorageIdentifier(builder);
3277: DatabaseMetaData metaData = activeConnection
3278: .getMetaData();
3279: ResultSet columnsSet = metaData
3280: .getColumns(null, null,
3281: tableName, null);
3282: try {
3283: while (columnsSet.next()) {
3284: if (columnsSet.getString(
3285: "COLUMN_NAME")
3286: .equals(fieldName)) {
3287: foundColumn = true;
3288: break;
3289: }
3290: }
3291: } finally {
3292: columnsSet.close();
3293: }
3294: } catch (java.sql.SQLException sqe) {
3295: log.error(sqe.getMessage());
3296: } finally {
3297: releaseActiveConnection();
3298: }
3299: List<MMObjectNode> nodes = builder
3300: .getNodes(new org.mmbase.storage.search.implementation.NodeSearchQuery(
3301: builder));
3302: log.service("Checking all "
3303: + nodes.size() + " nodes of '"
3304: + builder.getTableName() + "'");
3305: for (MMObjectNode node : nodes) {
3306: File storeFile = getBinaryFile(
3307: node, fieldName);
3308: if (!storeFile.exists()) { // not found!
3309: File legacyFile = getLegacyBinaryFile(
3310: node, fieldName);
3311: if (legacyFile != null) {
3312: storeFile.getParentFile()
3313: .mkdirs();
3314: if (legacyFile
3315: .renameTo(storeFile)) {
3316: log.service("Renamed "
3317: + legacyFile
3318: + " to "
3319: + storeFile);
3320: result++;
3321: } else {
3322: log
3323: .warn("Could not rename "
3324: + legacyFile
3325: + " to "
3326: + storeFile);
3327: }
3328: } else {
3329: if (foundColumn) {
3330:
3331: Blob b = getBlobFromDatabase(
3332: node, field,
3333: false);
3334: byte[] bytes = b
3335: .getBytes(
3336: 0L,
3337: (int) b
3338: .length());
3339: node.setValue(
3340: fieldName,
3341: bytes);
3342: storeBinaryAsFile(node,
3343: field);
3344:
3345: node
3346: .storeValue(
3347: fieldName,
3348: MMObjectNode.VALUE_SHORTED); // remove to avoid filling node-cache with lots of handles and cause out-of-memory
3349: // node.commit(); no need, because we only changed blob (so no database updates are done)
3350: result++;
3351: fromDatabase++;
3352: log
3353: .service("( "
3354: + result
3355: + ") Found bytes in database while configured to be on disk. Stored to "
3356: + storeFile);
3357: }
3358: }
3359: }
3360: } // nodes
3361: } // if type = byte
3362: } // fields
3363: }
3364: } // builders
3365: if (result > 0) {
3366: log
3367: .info("Converted "
3368: + result
3369: + " fields "
3370: + ((fromDatabase > 0 && fromDatabase < result) ? " of wich "
3371: + fromDatabase
3372: + " from database"
3373: : ""));
3374: if (fromDatabase > 0) {
3375: log
3376: .info("You may drop byte array columns from the database now. See the the VERIFY warning during initialisation.");
3377: }
3378: } else {
3379: log.service("Converted no fields");
3380: }
3381: return result;
3382: } // synchronized
3383: } else {
3384: // not configured to store blobs as file
3385: return -1;
3386: }
3387: }
3388:
3389: protected static class InputStreamBlob implements Blob {
3390: private InputStream inputStream;
3391: private byte[] bytes = null;
3392: private long size;
3393:
3394: public InputStreamBlob(InputStream is, long s) {
3395: inputStream = is;
3396: size = s;
3397: }
3398:
3399: public InputStreamBlob(InputStream is) {
3400: inputStream = is;
3401: size = -1;
3402: }
3403:
3404: public InputStream getBinaryStream() {
3405: if (bytes != null) {
3406: return new ByteArrayInputStream(bytes);
3407: } else {
3408: return inputStream;
3409: }
3410: }
3411:
3412: public InputStream getBinaryStream(long pos, long length) {
3413: return new ByteArrayInputStream(getBytes(pos, (int) length));
3414: }
3415:
3416: public byte[] getBytes(long pos, int length) {
3417: if (pos == 1 && size == length && bytes != null)
3418: return bytes;
3419:
3420: ByteArrayOutputStream b = new ByteArrayOutputStream();
3421: long p = 1;
3422: int c;
3423: InputStream stream = getBinaryStream();
3424: try {
3425: while ((c = stream.read()) > -1) {
3426: if (p >= pos) {
3427: b.write(c);
3428: }
3429: p++;
3430: if (p > pos + length)
3431: break;
3432: }
3433: } catch (IOException ioe) {
3434: log.error(ioe);
3435: }
3436: return b.toByteArray();
3437: }
3438:
3439: protected void getBytes() {
3440: ByteArrayOutputStream b = new ByteArrayOutputStream();
3441: int c;
3442: byte[] buf = new byte[1024];
3443: try {
3444: while ((c = inputStream.read(buf)) > -1) {
3445: b.write(buf, 0, c);
3446: }
3447: } catch (IOException ioe) {
3448: log.error(ioe);
3449: }
3450: bytes = b.toByteArray();
3451: size = bytes.length;
3452: }
3453:
3454: public long length() {
3455: if (size < 0 && inputStream != null) {
3456: getBytes();
3457: }
3458: return size;
3459: }
3460:
3461: public long position(Blob pattern, long start) {
3462: throw new UnsupportedOperationException("");
3463: }
3464:
3465: public long position(byte[] pattern, long start) {
3466: throw new UnsupportedOperationException("");
3467: }
3468:
3469: public OutputStream setBinaryStream(long pos) {
3470: throw new UnsupportedOperationException("");
3471: }
3472:
3473: public int setBytes(long pos, byte[] bytes) {
3474: throw new UnsupportedOperationException("");
3475: }
3476:
3477: public int setBytes(long pos, byte[] bytes, int offset, int len) {
3478: throw new UnsupportedOperationException("");
3479: }
3480:
3481: public void truncate(long len) {
3482: throw new UnsupportedOperationException("");
3483: }
3484:
3485: public void free() {
3486: bytes = null;
3487: if (inputStream != null) {
3488: try {
3489: inputStream.close();
3490: } catch (IOException ioe) {
3491: log.warn(ioe);
3492: }
3493: inputStream = null;
3494: }
3495: }
3496: }
3497: }
|