Source Code Cross Referenced for DatabaseStorageManager.java in  » Database-ORM » MMBase » org » mmbase » storage » implementation » database » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Database ORM » MMBase » org.mmbase.storage.implementation.database 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.