Source Code Cross Referenced for MMObjectBuilder.java in  » Database-ORM » MMBase » org » mmbase » module » core » 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.module.core 
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.module.core;
0011:
0012:        import java.io.File;
0013:        import java.net.URLEncoder;
0014:        import java.text.DateFormat;
0015:        import java.text.NumberFormat;
0016:        import java.util.*;
0017:
0018:        import org.mmbase.bridge.*;
0019:
0020:        import org.mmbase.cache.*;
0021:
0022:        import org.mmbase.datatypes.DataTypeCollector;
0023:
0024:        import org.mmbase.module.corebuilders.*;
0025:
0026:        import org.mmbase.core.*;
0027:        import org.mmbase.core.event.*;
0028:        import org.mmbase.core.util.Fields;
0029:        import org.mmbase.core.util.StorageConnector;
0030:
0031:        import org.mmbase.datatypes.DataType;
0032:
0033:        import org.mmbase.storage.StorageException;
0034:        import org.mmbase.storage.search.*;
0035:        import org.mmbase.storage.search.implementation.*;
0036:
0037:        import org.mmbase.util.*;
0038:        import org.mmbase.util.functions.*;
0039:        import org.mmbase.util.logging.Logger;
0040:        import org.mmbase.util.logging.Logging;
0041:
0042:        /**
0043:         * This class is the base class for all builders.
0044:         * It offers a list of routines which are useful in maintaining the nodes in the MMBase
0045:         * object cloud.
0046:         * <br />
0047:         * Builders are the core of the MMBase system. They create, delete and search the MMObjectNodes.
0048:         * Most manipulations concern nodes of that builders type. However, a number of retrieval routines extend
0049:         * beyond a builders scope and work on the cloud in general, allowing some ease in retrieval of nodes.
0050:         * The basic routines in this class can be extended to handle more specific demands for nodes.
0051:         * Most of these 'extended builders' will be stored in mmbase.org.builders or mmbase.org.corebuilders.
0052:         * Examples include relation builders or builders for handling binary data such as images.
0053:         * The various builders are registered by the 'TypeDef' builder class (one of the core builders, itself
0054:         * an extension of this class).
0055:         *
0056:         * @author Daniel Ockeloen
0057:         * @author Rob Vermeulen
0058:         * @author Pierre van Rooden
0059:         * @author Eduard Witteveen
0060:         * @author Johannes Verelst
0061:         * @author Rob van Maris
0062:         * @author Michiel Meeuwissen
0063:         * @author Ernst Bunders
0064:         * @version $Id: MMObjectBuilder.java,v 1.421 2007/11/16 09:47:34 michiel Exp $
0065:         */
0066:        public class MMObjectBuilder extends MMTable implements 
0067:                NodeEventListener, RelationEventListener {
0068:
0069:            /**
0070:             * Name of the field containing the object number, which uniquely identifies the node.
0071:             * @since MMBase-1.8
0072:             */
0073:            public static final String FIELD_NUMBER = "number";
0074:
0075:            /**
0076:             * Name of the field containing the owner. The owner field is used for security implementations.
0077:             * @since MMBase-1.8
0078:             */
0079:            public static final String FIELD_OWNER = "owner";
0080:
0081:            /**
0082:             * Name of the field containing the object type number. This refers to an entry in the 'typedef' builder table.
0083:             * @since MMBase-1.8
0084:             */
0085:            public static final String FIELD_OBJECT_TYPE = "otype";
0086:
0087:            /**
0088:             * @since MMBase-1.8
0089:             */
0090:            public static final String TMP_FIELD_NUMBER = "_number";
0091:            public static final String TMP_FIELD_EXISTS = "_exists";
0092:
0093:            /**
0094:             * Default (system) owner name for the owner field.
0095:             * @since MMBase-1.8
0096:             */
0097:            public static final String SYSTEM_OWNER = "system";
0098:
0099:            /** Default size of the temporary node cache */
0100:            public final static int TEMPNODE_DEFAULT_SIZE = 1024;
0101:
0102:            /** Default replacements for method getHTML() */
0103:            public final static String DEFAULT_ALINEA = "<br />&#160;<br />";
0104:            public final static String DEFAULT_EOL = "<br />";
0105:
0106:            public final static int EVENT_TYPE_LOCAL = 0;
0107:            public final static int EVENT_TYPE_REMOTE = 1;
0108:
0109:            /**
0110:             * Parameters for the age function
0111:             * @since MMBase-1.7
0112:             */
0113:            public final static Parameter<?>[] AGE_PARAMETERS = {};
0114:
0115:            /**
0116:             * Collection for temporary nodes,
0117:             * Used by the Temporarynodemanager when working with transactions
0118:             * The default size is 1024.
0119:             * @duplicate use Cache object instead
0120:             * @scope protected
0121:             */
0122:            public static Map<String, MMObjectNode> temporaryNodes = new Hashtable<String, MMObjectNode>(
0123:                    TEMPNODE_DEFAULT_SIZE);
0124:
0125:            /**
0126:             * Default output when no data is available to determine a node's GUI description
0127:             */
0128:            public static final String GUI_INDICATOR = "no info";
0129:
0130:            /**
0131:             * The cache for all blobs.
0132:             * @since 1.8.0
0133:             */
0134:            protected static BlobCache genericBlobCache = new BlobCache(200) {
0135:                public String getName() {
0136:                    return "GenericBlobCache";
0137:                }
0138:            };
0139:
0140:            static {
0141:                genericBlobCache.putCache();
0142:            }
0143:
0144:            /**
0145:             * The cache that contains the X last requested nodes
0146:             */
0147:            protected static org.mmbase.cache.NodeCache nodeCache = org.mmbase.cache.NodeCache
0148:                    .getCache();
0149:
0150:            /**
0151:             * Determines whether the cache is locked.
0152:             * A locked cache can be read, and nodes can be removed from it (allowing it to
0153:             * clean invalid nodes), but nodes cannot be added.
0154:             * Needed for committing nodes from transactions.
0155:             */
0156:            private static int cacheLocked = 0;
0157:
0158:            private static final Logger log = Logging
0159:                    .getLoggerInstance(MMObjectBuilder.class);
0160:
0161:            private List<MMObjectBuilder> descendants;
0162:
0163:            /**
0164:             * The string that can be used inside the builder.xml as property,
0165:             * to define the maximum number of nodes to return.
0166:             */
0167:            private static String MAX_NODES_FROM_QUERY_PROPERTY = "max-nodes-from-query";
0168:
0169:            /**
0170:             * The string that can be used inside the builder.xml as property,
0171:             * to set whether the builder broadcasts changes to nodes to eventlisteners.
0172:             */
0173:            private static String BROADCAST_CHANGES_PROPERTY = "broadcast-changes";
0174:
0175:            /**
0176:             * Description of the builder in the currently selected language
0177:             * Not that the first time the builder is created, this value is what is stored in the TypeDef table.
0178:             * @scope protected
0179:             */
0180:            public String description = "Base Object";
0181:
0182:            /**
0183:             * Descriptions of the builder per language
0184:             * Can be set with the &lt;descriptions&gt; tag in the xml builder file.
0185:             * @scope protected
0186:             */
0187:            public Hashtable<String, String> descriptions;
0188:
0189:            /**
0190:             * The default search age for this builder.
0191:             * Used for intializing editor search forms (see HtmlBase)
0192:             * Default value is 31. Can be changed with the &lt;searchage&gt; tag in the xml builder file.
0193:             * @scope protected
0194:             */
0195:            public String searchAge = "31";
0196:
0197:            /**
0198:             * Determines whether changes to this builder need be broadcast to other known mmbase servers.
0199:             */
0200:            protected boolean broadCastChanges = true;
0201:
0202:            /**
0203:             * Internal (instance) version number of this builder.
0204:             */
0205:            protected long internalVersion = -1;
0206:
0207:            /**
0208:             * The current builder's object type
0209:             * Retrieved from the TypeDef builder.
0210:             */
0211:            protected int oType = -1;
0212:
0213:            /**
0214:             *  Maintainer information for builder registration
0215:             *  Set with &lt;builder maintainer="mmbase.org" version="0"&gt; in the xml builder file
0216:             * @scope protected
0217:             */
0218:            String maintainer = "mmbase.org";
0219:
0220:            /** Collections of (GUI) names (singular) for the builder's objects, divided by language
0221:             * @scope protected
0222:             */
0223:            Hashtable<String, String> singularNames;
0224:
0225:            /** Collections of (GUI) names (plural) for the builder's objects, divided by language
0226:             * @scope protected
0227:             */
0228:            Hashtable<String, String> pluralNames;
0229:
0230:            /**
0231:             * Full filename (path + buildername + ".xml") where we loaded the builder from
0232:             * It is relative from the '/builders/' subdir
0233:             * @scope protected
0234:             */
0235:            String xmlPath = "";
0236:
0237:            /**
0238:             * Parameters for the GUI function
0239:             * @since MMBase-1.7
0240:             */
0241:            public final static Parameter<?>[] GUI_PARAMETERS = org.mmbase.util.functions.GuiFunction.PARAMETERS;
0242:
0243:            /**
0244:             * The famous GUI function as a function object.
0245:             * @since MMBase-1.8
0246:             */
0247:            protected Function<String> guiFunction = new GuiFunction();
0248:            {
0249:                addFunction(guiFunction);
0250:            }
0251:            /**
0252:             * Parameters constants for the NodeFunction {@link #wrapFunction}.
0253:             * @since MMBase-1.8
0254:             */
0255:            protected final static Parameter<?>[] WRAP_PARAMETERS = {
0256:                    new Parameter<String>(Parameter.FIELD, true),
0257:                    new Parameter<Number>("length", Number.class, Integer
0258:                            .valueOf(20)) };
0259:
0260:            /**
0261:             * This function wraps the text of a node's field and returns the result as a String.
0262:             * It takes as parameters a fieldname, the line length to wrap, and the Node containing the data.
0263:             * This function can be called through the function framework.
0264:             * @since MMBase-1.8
0265:             */
0266:            protected Function<String> wrapFunction = new NodeFunction<String>(
0267:                    "wrap", WRAP_PARAMETERS, ReturnType.STRING) {
0268:                {
0269:                    setDescription("This function wraps a field, word-by-word. You can use this, e.g. in <pre>-tags. This functionality should be available as an 'escaper', and this version should now be considered an example.");
0270:                }
0271:
0272:                public String getFunctionValue(Node node, Parameters parameters) {
0273:                    String val = node.getStringValue(parameters
0274:                            .getString(Parameter.FIELD));
0275:                    Number wrappos = (Number) parameters.get("length");
0276:                    return MMObjectBuilder.this .wrap(val, wrappos.intValue());
0277:                }
0278:            };
0279:            {
0280:                addFunction(wrapFunction);
0281:            }
0282:
0283:            /**
0284:             * Every Function Provider provides least the 'getFunctions' function, which returns a Set of all functions which it provides.
0285:             * This is overridden from FunctionProvider, because this one needs to be (also) a NodeFunction
0286:             * @since MMBase-1.8
0287:             */
0288:            protected Function<Collection<? extends Function>> getFunctions = new NodeFunction<Collection<? extends Function>>(
0289:                    "getFunctions", Parameter.emptyArray(),
0290:                    ReturnType.COLLECTION) {
0291:                {
0292:                    setDescription("The 'getFunctions' returns a Map of al Function object which are available on this FunctionProvider");
0293:                }
0294:
0295:                public Collection<? extends Function> getFunctionValue(
0296:                        Node node, Parameters parameters) {
0297:                    return MMObjectBuilder.this .getFunctions(getCoreNode(
0298:                            MMObjectBuilder.this , node));
0299:                }
0300:
0301:                public Collection<? extends Function> getFunctionValue(
0302:                        Parameters parameters) {
0303:                    Node node = parameters.get(Parameter.NODE);
0304:                    if (node == null) {
0305:                        return MMObjectBuilder.this .getFunctions();
0306:                    } else {
0307:                        return MMObjectBuilder.this .getFunctions(getCoreNode(
0308:                                MMObjectBuilder.this , node));
0309:                    }
0310:                }
0311:            };
0312:            {
0313:                addFunction(getFunctions);
0314:            }
0315:
0316:            /**
0317:             * The info-function is a node-function and a builder-function. Therefore it is defined as a node-function, but also overidesd getFunctionValue(Parameters).
0318:             * @since MMBase-1.8
0319:             */
0320:            protected Function<Object> infoFunction = new NodeFunction<Object>(
0321:                    "info", new Parameter[] { new Parameter<Object>("function",
0322:                            String.class) }, ReturnType.UNKNOWN) {
0323:                {
0324:                    setDescription("Returns information about available functions");
0325:                }
0326:
0327:                protected Object getFunctionValue(
0328:                        Collection<Function<?>> functions, Parameters parameters) {
0329:                    String function = (String) parameters.get("function");
0330:                    if (function == null || function.equals("")) {
0331:                        Map<String, String> info = new HashMap<String, String>();
0332:                        for (Function<?> f : functions) {
0333:                            info.put(f.getName(), f.getDescription());
0334:                        }
0335:                        return info;
0336:                    } else {
0337:                        Function<?> func = getFunction(function);
0338:                        if (func == null)
0339:                            return "No such function " + function;
0340:                        return func.getDescription();
0341:                    }
0342:                }
0343:
0344:                public Object getFunctionValue(Node node, Parameters parameters) {
0345:                    return getFunctionValue(MMObjectBuilder.this 
0346:                            .getFunctions(getCoreNode(MMObjectBuilder.this ,
0347:                                    node)), parameters);
0348:                }
0349:
0350:                public Object getFunctionValue(Parameters parameters) {
0351:                    MMObjectNode node = (MMObjectNode) parameters
0352:                            .get(Parameter.CORENODE);
0353:                    if (node == null) {
0354:                        return getFunctionValue(MMObjectBuilder.this 
0355:                                .getFunctions(), parameters);
0356:                    } else {
0357:                        return getFunctionValue(MMObjectBuilder.this 
0358:                                .getFunctions(node), parameters);
0359:                    }
0360:                }
0361:            };
0362:            {
0363:                addFunction(infoFunction);
0364:            }
0365:
0366:            // contains the builder's field definitions
0367:            protected final Map<String, CoreField> fields = new HashMap<String, CoreField>();
0368:
0369:            /**
0370:             * Determines whether a builder is virtual (data is not stored in the storage layer).
0371:             */
0372:            protected boolean virtual = false;
0373:
0374:            /**
0375:             *  Set of remote observers, which are notified when a node of this type changes
0376:             */
0377:            private final Set<MMBaseObserver> remoteObservers = Collections
0378:                    .synchronizedSet(new HashSet<MMBaseObserver>());
0379:
0380:            /**
0381:             * Set of local observers, which are notified when a node of this type changes
0382:             */
0383:            private final Set<MMBaseObserver> localObservers = Collections
0384:                    .synchronizedSet(new HashSet<MMBaseObserver>());
0385:
0386:            /**
0387:             * Reference to the builders that this builder extends.
0388:             * @since MMBase-1.6.2 (parentBuilder in 1.6.0)
0389:             */
0390:            private Stack<MMObjectBuilder> ancestors = new Stack<MMObjectBuilder>();
0391:
0392:            /**
0393:             * Version information for builder registration
0394:             * Set with &lt;builder maintainer="mmbase.org" version="0"&gt; in the xml
0395:             * builder file
0396:             */
0397:            private int version = 0;
0398:
0399:            /**
0400:             * Contains lists of builder fields in specified order
0401:             * (ORDER_CREATE, ORDER_EDIT, ORDER_LIST, ORDER_SEARCH)
0402:             */
0403:            private Map<Integer, List<CoreField>> sortedFieldLists = new HashMap<Integer, List<CoreField>>();
0404:
0405:            /** Properties of a specific Builder.
0406:             * Specified in the xml builder file with the <properties> tag.
0407:             * The use of properties is determined by builder
0408:             */
0409:            private final Map<String, String> properties = new HashMap<String, String>();
0410:
0411:            /**
0412:             * The datatype collector for this builder
0413:             */
0414:            private DataTypeCollector dataTypeCollector = null;
0415:
0416:            /**
0417:             * Constructor.
0418:             */
0419:            public MMObjectBuilder() {
0420:                storageConnector = new StorageConnector(this );
0421:            }
0422:
0423:            private void initAncestors() {
0424:                if (!ancestors.empty()) {
0425:                    ancestors.peek().init();
0426:                }
0427:            }
0428:
0429:            /**
0430:             * Initializes this builder
0431:             * The property 'mmb' needs to be set for the builder before this method can be called.
0432:             * The method retrieves data from the TypeDef builder, or adds data to that builder if the
0433:             * current builder is not yet registered.
0434:             * @return true if init was completed, false if uncompleted.
0435:             * @see #create
0436:             */
0437:            public boolean init() {
0438:                synchronized (mmb) { // synchronized on mmb because can only init builder if mmb is inited completely
0439:
0440:                    // skip initialisation if oType has been set (happend at end of init)
0441:                    // note that init can be called twice
0442:                    if (oType != -1)
0443:                        return true;
0444:
0445:                    log.debug("Init of builder " + getTableName());
0446:
0447:                    loadInitParameters();
0448:
0449:                    // first make sure parent builder is initalized
0450:                    initAncestors();
0451:
0452:                    String broadCastChangesProperty = getInitParameter(BROADCAST_CHANGES_PROPERTY);
0453:                    if (broadCastChangesProperty != null) {
0454:                        broadCastChanges = broadCastChangesProperty
0455:                                .equals("true");
0456:                    }
0457:
0458:                    if (!created()) {
0459:                        log.info("Creating table for builder " + tableName);
0460:                        if (!create()) {
0461:                            // can't create buildertable.
0462:                            // Throw an exception
0463:                            throw new BuilderConfigurationException(
0464:                                    "Cannot create table for " + getTableName()
0465:                                            + ".");
0466:                        }
0467:                        ;
0468:                    }
0469:                    TypeDef typeDef = mmb.getTypeDef();
0470:                    // only deteremine otype if typedef is available,
0471:                    // or this is typedef itself (have to start somewhere)
0472:                    if (((typeDef != null) && (typeDef.getObjectType() != -1))
0473:                            || (this  == typeDef)) {
0474:                        oType = typeDef.getIntValue(tableName);
0475:                        if (oType == -1) { // no object type number defined yet
0476:                            if (log.isDebugEnabled())
0477:                                log.debug("Creating typedef entry for "
0478:                                        + tableName);
0479:                            MMObjectNode node = typeDef
0480:                                    .getNewNode(SYSTEM_OWNER);
0481:                            node.storeValue("name", tableName);
0482:
0483:                            // This sucks:
0484:                            if (description == null)
0485:                                description = "not defined in this language";
0486:
0487:                            node.storeValue("description", description);
0488:
0489:                            try {
0490:                                oType = mmb.getStorageManager().createKey();
0491:                            } catch (StorageException se) {
0492:                                log.error(se.getMessage()
0493:                                        + Logging.stackTrace(se));
0494:                                return false;
0495:                            }
0496:
0497:                            log.debug("Got key " + oType);
0498:                            node.storeValue(FIELD_NUMBER, oType);
0499:                            // for typedef, set otype explictly, as it wasn't set in getNewNode()
0500:                            if (this  == typeDef) {
0501:                                node.storeValue(FIELD_OBJECT_TYPE, oType);
0502:                            }
0503:                            typeDef.insert(SYSTEM_OWNER, node, false);
0504:                            // for typedef, call it's parents init again, as otype is only now set
0505:                            if (this  == typeDef) {
0506:                                initAncestors();
0507:                            }
0508:                        }
0509:                    } else {
0510:                        // warn if typedef was not created
0511:                        // except for the 'object' and 'typedef' basic builders
0512:                        if (!tableName.equals("typedef")
0513:                                && !tableName.equals("object")) {
0514:                            log.warn("init(): for tablename(" + tableName
0515:                                    + ") -> can't get to typeDef");
0516:                            return false;
0517:                        }
0518:                    }
0519:                    // XXX: wtf
0520:                    // add temporary fields
0521:                    checkAddTmpField(TMP_FIELD_NUMBER);
0522:                    checkAddTmpField(TMP_FIELD_EXISTS);
0523:
0524:                    // get property of maximum number of queries..
0525:                    String property = getInitParameter(MAX_NODES_FROM_QUERY_PROPERTY);
0526:                    if (property != null) {
0527:                        try {
0528:                            maxNodesFromQuery = Integer.parseInt(property);
0529:                            log.debug(getTableName() + " returns no more than "
0530:                                    + maxNodesFromQuery
0531:                                    + " records from a query.");
0532:                        } catch (NumberFormatException nfe) {
0533:                            log.warn("property:"
0534:                                    + MAX_NODES_FROM_QUERY_PROPERTY
0535:                                    + " contained an invalid integer value:'"
0536:                                    + property + "'(" + nfe + ")");
0537:                        }
0538:                    }
0539:                }
0540:                update();
0541:
0542:                //now register it as a listener for events of it's own type
0543:                //this is only for backwards compatibility, to notify the MMBaseObserver's
0544:                MMBase.getMMBase().addNodeRelatedEventsListener(getTableName(),
0545:                        this );
0546:
0547:                return true;
0548:            }
0549:
0550:            /**
0551:             * Returns the builder object number, which also functions as the objecttype.
0552:             * This is the same value as the value of the 'otype' field of objects created by this builder
0553:             * (rather than created by its descendants).
0554:             * @return the builder number
0555:             * @since MMBase-1.8
0556:             */
0557:            public int getNumber() {
0558:                return oType;
0559:            }
0560:
0561:            /**
0562:             * Returns the objecttype (otype).
0563:             * By preference, use {@link #getNumber()} for future compatibility with the bridge NodeManager methods.
0564:             * @return the objecttype
0565:             */
0566:            public int getObjectType() {
0567:                return getNumber();
0568:            }
0569:
0570:            /**
0571:             * Updates the internal version number of this buidler;
0572:             */
0573:            protected void update() {
0574:                internalVersion = System.currentTimeMillis();
0575:            }
0576:
0577:            /**
0578:             * Returns the builder's internal version number.
0579:             * This number can be used to sync wrapper classes. I.e. to make sure that a
0580:             * nodemanager's fieldlist is the same as that of the wrapped builder.
0581:             */
0582:            public long getInternalVersion() {
0583:                return internalVersion;
0584:            }
0585:
0586:            /**
0587:             * Creates a new builder table in the storage layer.
0588:             */
0589:            public boolean create() {
0590:                log.debug(tableName);
0591:                try {
0592:                    mmb.getStorageManager().create(this );
0593:                    return true;
0594:                } catch (StorageException se) {
0595:                    log.error(se.getMessage() + Logging.stackTrace(se));
0596:                    return false;
0597:                }
0598:            }
0599:
0600:            /**
0601:             * Removes the builder from the storage.
0602:             * @since MMBase-1.7
0603:             */
0604:            public void delete() {
0605:                log.service("trying to drop table of builder: '" + tableName
0606:                        + "'");
0607:                mmb.getStorageManager().delete(this );
0608:            }
0609:
0610:            /**
0611:             * Tests whether the data in a node is valid (throws an exception if this is not the case).
0612:             * @param node The node whose data to check
0613:             * @throws org.mmbase.module.core.InvalidDataException
0614:             *   If the data was unrecoverably invalid (the references did not point to existing objects)
0615:             */
0616:            public void testValidData(MMObjectNode node)
0617:                    throws InvalidDataException {
0618:                return;
0619:            };
0620:
0621:            /**
0622:             * Insert a new, empty, object of a certain type.
0623:             * @param oType The type of object to create
0624:             * @param owner The administrator creating the node
0625:             * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
0626:             *        The basic routine does not create any nodes this way and always fails.
0627:             */
0628:            public int insert(int oType, String owner) {
0629:                return -1;
0630:            }
0631:
0632:            /**
0633:             * Insert a new object (content provided) in the cloud, including an entry for the object alias (if provided).
0634:             * This method indirectly calls {@link #preCommit}.
0635:             * @param owner The administrator creating the node
0636:             * @param node The object to insert. The object need be of the same type as the current builder.
0637:             * @return An <code>int</code> value which is the new object's unique number, -1 if the insert failed.
0638:             */
0639:            public int insert(String owner, MMObjectNode node) {
0640:                int n = mmb.getStorageManager().create(node);
0641:                if (n >= 0) {
0642:                    node.isNew = false;
0643:                }
0644:
0645:                node.useAliases();
0646:
0647:                // it is in the storage now, all caches can allready be invalidated, this makes sure
0648:                // that imediate 'select' after 'insert' will be correct'.
0649:                //xxx: this is bad.let's kill it!
0650:                //QueryResultCache.invalidateAll(node, NodeEvent.TYPE_NEW);
0651:                if (n <= 0) {
0652:                    log.warn("Did not get valid nodeNumber of storage " + n);
0653:                }
0654:                Integer nodeNumber = Integer.valueOf(n);
0655:                if (isNodeCached(nodeNumber)) {
0656:                    // it seems that something put the node in the cache already.
0657:                    // This is usually because the ChangeManager indirectly called 'getNode'
0658:                    // This should in the new event-mechanism not be needed, because the NodeEvent
0659:                    // contains the node.
0660:                    //log.warn("New node '" + n + "' of type " + node.parent.getTableName() + " is already in node-cache!" + Logging.stackTrace());
0661:                } else {
0662:                    safeCache(nodeNumber, node);
0663:                }
0664:                return n;
0665:            }
0666:
0667:            /**
0668:             * This method is called before an actual write to the storage layer is performed.
0669:             * @param node The node to be committed.
0670:             * @return the node to be committed (possibly after changes have been made).
0671:             */
0672:            public MMObjectNode preCommit(MMObjectNode node) {
0673:                return node;
0674:            }
0675:
0676:            /**
0677:             * Commit changes to this node to the storage layer. This method indirectly calls {@link #preCommit}.
0678:             * Use only to commit changes - for adding node, use {@link #insert}.
0679:             * @param node The node to be committed
0680:             * @return true if commit successful
0681:             */
0682:            public boolean commit(MMObjectNode node) {
0683:                mmb.getStorageManager().change(node);
0684:                return true;
0685:            }
0686:
0687:            /**
0688:             * Determines whether changes to this builder need be broadcast to other known mmbase servers.
0689:             * This setting also governs whether the cache for relation builders is emptied when a relation changes.
0690:             * Actual broadcasting (and cache emptying) is initiated in the storage layer, when
0691:             * changes are commited.
0692:             * By default, all builders broadcast their changes, with the exception of the TypeDef builder.
0693:             *
0694:             * MM: Can somebody please explain _why_ typedef node changes, like e.g. creating a new node type are _not_ broadcast.
0695:             * @since MMBase-1.8
0696:             */
0697:            public boolean broadcastChanges() {
0698:                return broadCastChanges;
0699:            }
0700:
0701:            /**
0702:             *  Creates an alias for a node, provided the OAlias builder is loaded.
0703:             *  @param number the to-be-aliased node's unique number
0704:             *  @param alias the aliasname to associate with the object
0705:             *  @param owner the owner of the alias
0706:             *  @since MMBase-1.8
0707:             *  @return if the alias could be created
0708:             */
0709:            public boolean createAlias(int number, String alias, String owner) {
0710:                if (mmb.getOAlias() != null) {
0711:                    if (getNode(alias) != null) { // this alias already exists! Don't add a new one!
0712:                        return false;
0713:                    }
0714:                    mmb.getOAlias().createAlias(alias, number, owner);
0715:                    return true;
0716:                } else {
0717:                    return false;
0718:                }
0719:            }
0720:
0721:            /**
0722:             *  Creates an alias for a node, provided the OAlias builder is loaded.
0723:             *  @param number the to-be-aliased node's unique number
0724:             *  @param alias the aliasname to associate with the object
0725:             *  @return if the alias could be created
0726:             */
0727:            public boolean createAlias(int number, String alias) {
0728:                return createAlias(number, alias, "system");
0729:            }
0730:
0731:            /**
0732:             * Returns the builder that this builder extends.
0733:             *
0734:             * @since MMBase-1.6
0735:             * @return the extended (parent) builder, or null if not available
0736:             */
0737:            public MMObjectBuilder getParentBuilder() {
0738:                if (ancestors.empty())
0739:                    return null;
0740:                return ancestors.peek();
0741:            }
0742:
0743:            /**
0744:             * Gives the list of parent-builders.
0745:             *
0746:             * @since MMBase-1.6.2
0747:
0748:             */
0749:            public List<MMObjectBuilder> getAncestors() {
0750:                return Collections.unmodifiableList(ancestors);
0751:            }
0752:
0753:            /**
0754:             * Creates list of descendant-builders.
0755:             *
0756:             * @since MMBase-1.6.2
0757:             */
0758:            public List<MMObjectBuilder> getDescendants() {
0759:                if (descendants == null) {
0760:                    List<MMObjectBuilder> result = new ArrayList<MMObjectBuilder>();
0761:                    for (MMObjectBuilder builder : mmb.getBuilders()) {
0762:                        if (builder.isExtensionOf(this )) {
0763:                            result.add(builder);
0764:                        }
0765:                    }
0766:                    if (mmb.getState()) {
0767:                        // for some reason it gets a bit confused if this is done earlier
0768:                        // I don't quite know why
0769:                        descendants = result;
0770:                    }
0771:                    return result;
0772:                }
0773:                return descendants;
0774:            }
0775:
0776:            /**
0777:             * Sets the builder that this builder extends, and registers it in the storage layer.
0778:             * @param parent the extended (parent) builder, or null if not available
0779:             *
0780:             * @since MMBase-1.6
0781:             */
0782:            public void setParentBuilder(MMObjectBuilder parent) {
0783:                ancestors.addAll(parent.getAncestors());
0784:                ancestors.push(parent);
0785:                getDataTypeCollector().addCollector(
0786:                        parent.getDataTypeCollector());
0787:            }
0788:
0789:            /**
0790:             * Returns the datatype collector belonging to this buidler.
0791:             * A datatype collector contains the datatypes that are local to this builder.
0792:             * @since MMBase-1.8
0793:             */
0794:            public DataTypeCollector getDataTypeCollector() {
0795:                if (dataTypeCollector == null) {
0796:                    Object signature = new String(getTableName() + "_"
0797:                            + System.currentTimeMillis());
0798:                    dataTypeCollector = new DataTypeCollector(signature);
0799:                }
0800:                return dataTypeCollector;
0801:            }
0802:
0803:            /**
0804:             * Checks wether this builder is an extension of the argument builder
0805:             *
0806:             * @since MMBase-1.6.2
0807:             */
0808:            public boolean isExtensionOf(MMObjectBuilder o) {
0809:                return ancestors.contains(o);
0810:            }
0811:
0812:            /**
0813:             * Get a new node, using this builder as its parent. The new node is not a part of the cloud
0814:             * yet, and thus has the value -1 as a number. (Call {@link #insert} to add the node to the
0815:             * cloud).
0816:             * @param owner The administrator creating the new node.
0817:             * @return A newly initialized <code>MMObjectNode</code>.
0818:             */
0819:            public MMObjectNode getNewNode(String owner) {
0820:                MMObjectNode node = getEmptyNode(owner);
0821:                setDefaults(node);
0822:                node.isNew = true;
0823:                return node;
0824:            }
0825:
0826:            /**
0827:             * Returns a new empty node object. This is used by Storage to create a non-new node object (isNew is false), which is then
0828:             * be filled with actual values from storage.
0829:             * @since MMBase-1.8.
0830:             */
0831:            public MMObjectNode getEmptyNode(String owner) {
0832:                MMObjectNode node = new MMObjectNode(this , false);
0833:                node.setValue(FIELD_NUMBER, -1);
0834:                node.setValue(FIELD_OWNER, owner);
0835:                node.setValue(FIELD_OBJECT_TYPE, oType);
0836:                return node;
0837:            }
0838:
0839:            /**
0840:             * Sets defaults for a node. Fields "number", "owner" and "otype" are not set by this method.
0841:             * @param node The node to set the defaults of.
0842:             */
0843:            public void setDefaults(MMObjectNode node) {
0844:                for (CoreField field : getFields()) {
0845:                    if (field.isVirtual())
0846:                        continue;
0847:                    if (field.getName().equals(FIELD_NUMBER))
0848:                        continue;
0849:                    if (field.getName().equals(FIELD_OWNER))
0850:                        continue;
0851:                    if (field.getName().equals(FIELD_OBJECT_TYPE))
0852:                        continue;
0853:                    if (field.getType() == Field.TYPE_NODE)
0854:                        continue;
0855:
0856:                    Object defaultValue = field.getDataType().getDefaultValue();
0857:                    if ((defaultValue == null) && field.isNotNull()) {
0858:                        Class clazz = Fields.typeToClass(field.getType());
0859:                        if (clazz != null) {
0860:                            defaultValue = Casting.toType(clazz, null, "");
0861:                        } else {
0862:                            log.warn("No class found for type of " + field);
0863:                        }
0864:                    }
0865:                    node.setValue(field.getName(), defaultValue);
0866:                }
0867:            }
0868:
0869:            /**
0870:             * In setDefault you could want to generate unique values for fields (if the field is 'unique').
0871:             * @since MMBase-1.7
0872:             */
0873:            protected String setUniqueValue(MMObjectNode node, String field,
0874:                    String baseValue) {
0875:                int seq = 0;
0876:                boolean found = false;
0877:                String value = baseValue;
0878:                try {
0879:                    while (!found) {
0880:                        NodeSearchQuery query = new NodeSearchQuery(this );
0881:                        value = baseValue + seq;
0882:                        BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
0883:                                query.getField(getField(field)), value);
0884:                        query.setConstraint(constraint);
0885:                        if (getNodes(query).size() == 0) {
0886:                            found = true;
0887:                            break;
0888:                        }
0889:                        seq++;
0890:                    }
0891:                } catch (SearchQueryException e) {
0892:                    value = baseValue + System.currentTimeMillis();
0893:                }
0894:                node.setValue(field, value);
0895:                return value;
0896:            }
0897:
0898:            /**
0899:             * In setDefault you could want to generate unique values for fields (if the field is 'unique').
0900:             * @since MMBase-1.7
0901:             */
0902:            protected int setUniqueValue(MMObjectNode node, String field,
0903:                    int offset) {
0904:                int seq = offset;
0905:                boolean found = false;
0906:                try {
0907:                    while (!found) {
0908:                        NodeSearchQuery query = new NodeSearchQuery(this );
0909:                        BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
0910:                                query.getField(getField(field)), Integer
0911:                                        .valueOf(seq));
0912:                        query.setConstraint(constraint);
0913:                        if (getNodes(query).size() == 0) {
0914:                            found = true;
0915:                            break;
0916:                        }
0917:                        seq++;
0918:                    }
0919:                } catch (SearchQueryException e) {
0920:                    seq = (int) System.currentTimeMillis() / 1000;
0921:                }
0922:                node.setValue(field, seq);
0923:                return seq;
0924:            }
0925:
0926:            /**
0927:             * Remove a node from the cloud.
0928:             * @param node The node to remove.
0929:             */
0930:            public void removeNode(MMObjectNode node) {
0931:                if (oType != node.getOType()) {
0932:                    // fixed comment's below..??
0933:                    // prevent from making storage inconsistent(say remove nodes from inactive builder)
0934:                    // the builder we are in is not the actual builder!!
0935:                    // ? why not an node.remove()
0936:                    throw new RuntimeException(
0937:                            "Builder with name: "
0938:                                    + getTableName()
0939:                                    + "(otype "
0940:                                    + oType
0941:                                    + ") is not the actual builder of the node that is to be deleted: "
0942:                                    + node.getNumber() + " (otype: "
0943:                                    + node.getOType() + ")");
0944:                }
0945:
0946:                removeSyncNodes(node);
0947:
0948:                clearBlobCache(node.getNumber());
0949:
0950:                // removes the node FROM THIS BUILDER
0951:                // seems not a very logical call, as node.parent is the node's actual builder,
0952:                // which may - possibly - be very different from the current builder
0953:                mmb.getStorageManager().delete(node);
0954:
0955:                // change is in storage, caches can be invalidated immediately
0956:                //really bad!!!
0957:                //QueryResultCache.invalidateAll(node, NodeEvent.EVENT_TYPE_DELETE);
0958:            }
0959:
0960:            /**
0961:             * Removes the syncnodes to this node. This is logical, but also needed to maintain storage
0962:             * integrety.
0963:             *
0964:             * @since MMBase-1.7
0965:             */
0966:            protected void removeSyncNodes(MMObjectNode node) {
0967:                try {
0968:                    MMObjectBuilder syncnodes = mmb.getBuilder("syncnodes");
0969:                    NodeSearchQuery query = new NodeSearchQuery(syncnodes);
0970:                    Integer numericalValue = node.getNumber();
0971:                    BasicStepField field = query.getField(syncnodes
0972:                            .getField("localnumber"));
0973:                    BasicFieldValueConstraint constraint = new BasicFieldValueConstraint(
0974:                            field, numericalValue);
0975:                    query.setConstraint(constraint);
0976:                    for (MMObjectNode syncnode : syncnodes.getNodes(query)) {
0977:                        syncnode.parent.removeNode(syncnode);
0978:                        if (log.isDebugEnabled()) {
0979:                            log.debug("Removed syncnode " + syncnode);
0980:                        }
0981:                    }
0982:                } catch (SearchQueryException e) {
0983:                    throw new RuntimeException(e);
0984:                }
0985:            }
0986:
0987:            /**
0988:             * Remove the relations of a node.
0989:             * @param node The node whose relations to remove.
0990:             */
0991:            public void removeRelations(MMObjectNode node) {
0992:                List<MMObjectNode> relsv = getRelations_main(node.getNumber());
0993:                if (relsv != null) {
0994:                    for (MMObjectNode relnode : relsv) {
0995:                        // determine the true builder for this node
0996:                        // (node.parent is always InsRel, but otype
0997:                        //  indicates any derived builders, such as AuthRel)
0998:                        MMObjectBuilder bul = mmb.getMMObject(mmb.getTypeDef()
0999:                                .getValue(relnode.getOType()));
1000:                        // remove the node using this builder
1001:                        // circumvent problem in storage layer (?)
1002:                        bul.removeNode(relnode);
1003:                    }
1004:                }
1005:            }
1006:
1007:            /**
1008:             * Is this node cached at this moment?
1009:             * @param number The number of the node to check.
1010:             * @return <code>true</code> if the node is in the cache, <code>false</code> otherwise
1011:             */
1012:            public boolean isNodeCached(Integer number) {
1013:                return nodeCache.containsKey(number);
1014:            }
1015:
1016:            /**
1017:             * Retrieves a node from the cache, or <code>null</code> if it doesn't exist.
1018:             * @param number The number of the node to retrieve.
1019:             * @return an MMObjectNode or <code>null</code> if the node is not in the cache
1020:             * @todo This is a simple wrapper around node cache, why not expose node cache in stead?
1021:             * @since MMBase-1.8
1022:             */
1023:            public MMObjectNode getNodeFromCache(Integer number) {
1024:                return nodeCache.get(number);
1025:            }
1026:
1027:            /**
1028:             * Stores a node in the cache provided the cache is not write locked.
1029:             * @return a valid node. If the node already was in the cache, the cached node is returned.
1030:             * In that case the node given as parameter should become invalid
1031:             */
1032:            public MMObjectNode safeCache(Integer n, MMObjectNode node) {
1033:                MMObjectNode retval = getNodeFromCache(n);
1034:                if (retval != null) {
1035:                    return retval;
1036:                } else {
1037:                    synchronized (nodeCache) {
1038:                        if (cacheLocked == 0) {
1039:                            nodeCache.put(n, node);
1040:                        }
1041:                    }
1042:                    return node;
1043:                }
1044:            }
1045:
1046:            /**
1047:             * Locks the node cache during the commit of a node.  This prevents the cache from gaining an
1048:             * invalid state during the commit.
1049:             *
1050:             * Basicly the goals is to ensure that nothing is put into the cache during a commit of a node,
1051:             * because that may be the wrong node then.
1052:             */
1053:            boolean safeCommit(MMObjectNode node) {
1054:                boolean res = false;
1055:                try {
1056:                    synchronized (nodeCache) {
1057:                        cacheLocked++;
1058:                    }
1059:                    if (node.getNumber() > 0) {
1060:                        nodeCache.remove(node.getNumber());
1061:                    }
1062:
1063:                    res = node.commit();
1064:                } finally {
1065:                    synchronized (nodeCache) {
1066:                        cacheLocked--;
1067:                    }
1068:                }
1069:                return res;
1070:            }
1071:
1072:            /**
1073:             * Locks the node cache during the insert of a node.
1074:             * This prevents the cache from adding the node, which
1075:             * means that the next time the node is read it is 'refreshed'
1076:             * from the storage
1077:             */
1078:            int safeInsert(MMObjectNode node, String userName) {
1079:                int res = -1;
1080:                try {
1081:                    synchronized (nodeCache) {
1082:                        cacheLocked++;
1083:                    }
1084:                    // determine valid username
1085:                    if ((userName == null) || (userName.length() <= 1)) { // may not have owner of 1 char??
1086:                        userName = node.getStringValue(FIELD_OWNER);
1087:                        if (log.isDebugEnabled()) {
1088:                            log.debug("Found username "
1089:                                    + (userName == null ? "NULL" : userName));
1090:                        }
1091:                    }
1092:                    res = node.insert(userName);
1093:                    if (res > -1) {
1094:                        nodeCache.put(Integer.valueOf(res), node);
1095:                    }
1096:                } finally {
1097:                    synchronized (nodeCache) {
1098:                        cacheLocked--;
1099:                    }
1100:                }
1101:                return res;
1102:            }
1103:
1104:            /**
1105:             * Determine whether this builder is virtual.
1106:             * A virtual builder represents nodes that are not stored or retrieved directly
1107:             * from storage, but are created as needed.
1108:             * @return <code>true</code> if the builder is virtual.
1109:             */
1110:            public boolean isVirtual() {
1111:                return virtual;
1112:            }
1113:
1114:            /**
1115:             * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1116:             * or the string-form of an integer value (the number field of an object node).
1117:             * Note that the OAlias builder needs to be active for the alias to be used
1118:             * (otherwise using an alias is concidered invalid).
1119:             * @param key The value to search for
1120:             * @param useCache If true, the node is retrieved from the node cache if possible.
1121:             * @return <code>null</code> if the node does not exist or the key is invalid, or a
1122:             *       <code>MMObjectNode</code> containing the contents of the requested node.
1123:             */
1124:            public MMObjectNode getNode(String key, boolean useCache) {
1125:                if (key == null) {
1126:                    log.error("getNode(null) for builder '" + tableName
1127:                            + "': key is null!");
1128:                    // who is doing that?
1129:                    log.info(Logging.stackTrace(6));
1130:                    return null;
1131:                }
1132:                int nr = -1;
1133:                // first look if we have a number...
1134:                try {
1135:                    nr = Integer.parseInt(key);
1136:                } catch (Exception e) {
1137:                }
1138:                if (nr != -1) {
1139:                    // key passed was a number.
1140:                    // return node with this number
1141:                    return getNode(nr, useCache);
1142:                } else {
1143:                    // key passed was an alias
1144:                    // return node with this alias
1145:                    log.debug("Getting node by alias");
1146:                    if (mmb.getOAlias() != null) {
1147:                        return mmb.getOAlias().getAliasedNode(key);
1148:                    } else {
1149:                        return null;
1150:                    }
1151:                }
1152:            }
1153:
1154:            /**
1155:             * Retrieves a node based on a unique key. The key is either an entry from the OAlias table
1156:             * or the string-form of an integer value (the number field of an object node).
1157:             * Retrieves a node from the node cache if possible.
1158:             * @param key The value to search for
1159:             * @return <code>null</code> if the node does not exist or the key is invalid, or a
1160:             *       <code>MMObjectNode</code> containing the contents of the requested node.
1161:             */
1162:            public MMObjectNode getNode(String key) {
1163:                return getNode(key, true);
1164:            }
1165:
1166:            /**
1167:             * Retrieves a node based on it's number (a unique key), retrieving the node
1168:             * from the node cache if possible.
1169:             * @param number The number of the node to search for
1170:             * @return <code>null</code> if the node does not exist or the key is invalid, or a
1171:             *       <code>MMObjectNode</code> containign the contents of the requested node.
1172:             */
1173:            public MMObjectNode getNode(int number) {
1174:                return getNode(number, true);
1175:            }
1176:
1177:            /**
1178:             * Create a new temporary node and put it in the temporary _exist
1179:             * node space
1180:             */
1181:            protected MMObjectNode getNewTmpNode(String owner, String key) {
1182:                MMObjectNode node = getNewNode(owner);
1183:                putTmpNode(key, node);
1184:                return node;
1185:            }
1186:
1187:            /**
1188:             * Put a Node in the temporary node list
1189:             * @param key  The (temporary) key under which to store the node
1190:             * @param node The node to store
1191:             */
1192:            static void putTmpNode(String key, MMObjectNode node) {
1193:                node.storeValue(TMP_FIELD_NUMBER, key);
1194:                temporaryNodes.put(key, node);
1195:            }
1196:
1197:            /**
1198:             * Defines a virtual field to use for temporary nodes. If the given field-name does not start
1199:             * with underscore ('_'), wich it usually does, then the field does also get a 'dbpos' (1000) as if it
1200:             * was actually present in the builder's XML as a virtual field (this is accompanied with a log
1201:             * message).
1202:             *
1203:             * Normally this is used to add 'tmp' fields like _number, _exists and _snumber which are system
1204:             * fields which are normally invisible.
1205:             *
1206:             * @param field the name of the temporary field
1207:             * @return true if the field was added, false if it already existed.
1208:             */
1209:            public boolean checkAddTmpField(String field) {
1210:                if (getDBState(field) == Field.STATE_UNKNOWN) { // means that field is not yet defined.
1211:                    CoreField fd = Fields.createField(field, Field.TYPE_STRING,
1212:                            Field.TYPE_UNKNOWN, Field.STATE_VIRTUAL, null);
1213:                    if (!fd.isTemporary()) {
1214:                        fd.setStoragePosition(1000);
1215:                        log
1216:                                .service("Added a virtual field '"
1217:                                        + field
1218:                                        + "' to builder '"
1219:                                        + getTableName()
1220:                                        + "' because it was not defined in the builder's XML, but the implementation requires it to exist.");
1221:                    } else {
1222:                        log.debug("Adding tmp (virtual) field '" + field
1223:                                + "' to builder '" + getTableName() + "'");
1224:                    }
1225:
1226:                    fd.setParent(this );
1227:                    fd.finish();
1228:
1229:                    addField(fd);
1230:                    // added field, so update version
1231:                    update();
1232:                    return true;
1233:                } else {
1234:                    return false;
1235:                }
1236:            }
1237:
1238:            /**
1239:             * Get nodes from the temporary node space
1240:             * @param key  The (temporary) key to use under which the node is stored
1241:             */
1242:            static protected MMObjectNode getTmpNode(String key) {
1243:                MMObjectNode node = temporaryNodes.get(key);
1244:                if (node == null && log.isTraceEnabled()) {
1245:                    log.trace("getTmpNode(): node not found " + key);
1246:                }
1247:                return node;
1248:            }
1249:
1250:            /**
1251:             * Remove a node from the temporary node space
1252:             * @param key  The (temporary) key under which the node is stored
1253:             */
1254:            static void removeTmpNode(String key) {
1255:                MMObjectNode node = temporaryNodes.remove(key);
1256:                if (node == null) {
1257:                    log.debug("removeTmpNode: node with " + key
1258:                            + " didn't exists");
1259:                }
1260:            }
1261:
1262:            /**
1263:             * Return a copy of the list  of field definitions of this table.
1264:             * @return An unmodifiable <code>Collection</code> with the tables fields
1265:             */
1266:            public Collection<CoreField> getFields() {
1267:                return Collections.unmodifiableCollection(fields.values());
1268:            }
1269:
1270:            /**
1271:             * Return a list of field names of this table.
1272:             * @return a unmodifiable <code>Set</code> with the tables field names
1273:             * @todo return an unmodifiable Set.
1274:             */
1275:            public Set<String> getFieldNames() {
1276:                return Collections.unmodifiableSet(fields.keySet());
1277:            }
1278:
1279:            /**
1280:             * Return a field's definition
1281:             * @param fieldName the requested field's name
1282:             * @return a <code>FieldDefs</code> belonging with the indicated field
1283:             * @todo  Should return CoreField
1284:             */
1285:            public FieldDefs getField(String fieldName) {
1286:                return (FieldDefs) fields.get(fieldName.toLowerCase());
1287:            }
1288:
1289:            /**
1290:             * @since MMBase-1.8
1291:             */
1292:            public boolean hasField(String fieldName) {
1293:                return fields.containsKey(fieldName.toLowerCase());
1294:            }
1295:
1296:            /**
1297:             * Clears all field list caches, and recalculates the field list.
1298:             */
1299:            protected void updateFields() {
1300:                sortedFieldLists.clear();
1301:                update();
1302:            }
1303:
1304:            /**
1305:             * Add a field to this builder.
1306:             * This does not affect the builder config file, nor the table used.
1307:             * @param def the field definiton to add
1308:             */
1309:            public void addField(CoreField def) {
1310:                Object oldField = fields.put(def.getName().toLowerCase(), def);
1311:                if (oldField != null) {
1312:                    log.warn("Replaced " + oldField + " !!");
1313:                }
1314:                updateFields();
1315:            }
1316:
1317:            /**
1318:             * Remove a field from this builder.
1319:             * This does not affect the builder config file, nor the table used.
1320:             * @param fieldName the name of the field to remove
1321:             */
1322:            public void removeField(String fieldName) {
1323:                CoreField def = getField(fieldName);
1324:                int dbpos = def.getStoragePosition();
1325:                fields.remove(fieldName);
1326:                for (Object element : fields.values()) {
1327:                    def = (CoreField) element;
1328:                    int curpos = def.getStoragePosition();
1329:                    if (curpos >= dbpos)
1330:                        def.setStoragePosition(curpos - 1);
1331:                }
1332:                updateFields();
1333:            }
1334:
1335:            /**
1336:             * Return a field's storage type. The returned value is one of the following values
1337:             * declared in Field:
1338:             * TYPE_STRING,
1339:             * TYPE_INTEGER,
1340:             * TYPE_BINARY,
1341:             * TYPE_FLOAT,
1342:             * TYPE_DOUBLE,
1343:             * TYPE_LONG,
1344:             * TYPE_NODE,
1345:             * TYPE_UNKNOWN
1346:             * @param fieldName the requested field's name
1347:             * @return the field's type.
1348:             */
1349:            public int getDBType(String fieldName) {
1350:                if (fields == null) {
1351:                    log.error("getDBType(): fields are null on object : "
1352:                            + tableName);
1353:                    return Field.TYPE_UNKNOWN;
1354:                }
1355:                Field field = getField(fieldName);
1356:                if (field == null) {
1357:                    //perhaps prefixed with own tableName[0-9]? (allowed since MMBase-1.7)
1358:                    int dot = fieldName.indexOf('.');
1359:                    if (dot > 0) {
1360:                        if (fieldName.startsWith(tableName)) {
1361:                            if (tableName.length() <= dot
1362:                                    || Character.isDigit(fieldName
1363:                                            .charAt(dot - 1))) {
1364:                                fieldName = fieldName.substring(dot + 1);
1365:                                field = getField(fieldName);
1366:                            }
1367:                        }
1368:                    }
1369:                }
1370:
1371:                if (field == null) {
1372:
1373:                    // log warning, except for virtual builders
1374:                    if (!virtual) { // should getDBType not be overridden in Virtual Builder then?
1375:                        log
1376:                                .warn("getDBType(): Can't find definition on field '"
1377:                                        + fieldName
1378:                                        + "' of builder "
1379:                                        + tableName);
1380:                        log.debug(Logging.stackTrace());
1381:                    }
1382:                    return Field.TYPE_UNKNOWN;
1383:                }
1384:                return field.getType();
1385:            }
1386:
1387:            /**
1388:             * Return a field's storage state. The returned value is one of the following values
1389:             * declared in Field:
1390:             * STATE_VIRTUAL,
1391:             * STATE_PERSISTENT,
1392:             * STATE_SYSTEM,
1393:             * STATE_UNKNOWN
1394:             * @param fieldName the requested field's name
1395:             * @return the field's state.
1396:             */
1397:            public int getDBState(String fieldName) {
1398:                if (fields == null)
1399:                    return Field.STATE_UNKNOWN;
1400:                Field field = getField(fieldName);
1401:                if (field == null)
1402:                    return Field.STATE_UNKNOWN;
1403:                return field.getState();
1404:            }
1405:
1406:            /**
1407:             * A complicated default implementation for GUI.
1408:             * @since MMBase-1.8
1409:             */
1410:            public String getGUIIndicator(MMObjectNode node, Parameters pars) {
1411:                Locale locale = pars.get(Parameter.LOCALE);
1412:                String language = pars.get(Parameter.LANGUAGE);
1413:                if (locale == null) {
1414:                    if (language != null) {
1415:                        locale = new Locale(language, "");
1416:                    }
1417:                } else {
1418:                    if (language != null
1419:                            && (!locale.getLanguage().equals(language))) { // odd, but well,
1420:                        locale = new Locale(language, locale.getCountry());
1421:                    }
1422:                }
1423:                if (locale == null)
1424:                    locale = mmb.getLocale();
1425:
1426:                if (log.isDebugEnabled()) {
1427:                    log.debug("language " + locale.getLanguage() + " country "
1428:                            + locale.getCountry());
1429:                }
1430:
1431:                String rtn;
1432:                String field = pars.getString("field");
1433:
1434:                if (locale == null) {
1435:                    if (field == null || "".equals(field)) {
1436:                        rtn = getGUIIndicator(node);
1437:                        if (rtn == GUI_INDICATOR) { // not overridden
1438:                            rtn = getNodeGUIIndicator(node, pars);
1439:                        }
1440:                    } else {
1441:                        rtn = getGUIIndicator(field, node);
1442:                    }
1443:                } else {
1444:                    if (field == null || "".equals(field)) {
1445:                        rtn = getLocaleGUIIndicator(locale, node);
1446:                        if (rtn == GUI_INDICATOR) { // not overridden
1447:                            rtn = getNodeGUIIndicator(node, pars);
1448:                        }
1449:                    } else {
1450:                        rtn = getLocaleGUIIndicator(locale, field, node);
1451:                    }
1452:                }
1453:
1454:                if (rtn == null) {
1455:                    CoreField fdef = getField(field);
1456:
1457:                    Object returnValue;
1458:                    if (fdef != null) {
1459:                        // test if the value can be derived from the enumerationlist of a datatype
1460:                        DataType dataType = fdef.getDataType();
1461:                        if (dataType instanceof  org.mmbase.datatypes.BinaryDataType) {
1462:                            returnValue = node.isNull(field) ? "" : ""
1463:                                    + node.getSize(field) + " byte";
1464:                        } else {
1465:                            returnValue = dataType.getEnumerationValue(locale,
1466:                                    pars.get(Parameter.CLOUD), pars
1467:                                            .get(Parameter.NODE), fdef, node
1468:                                            .getStringValue(field));
1469:                        }
1470:                    } else {
1471:                        returnValue = null;
1472:                    }
1473:                    if (returnValue != null) {
1474:                        rtn = returnValue.toString();
1475:                    } else {
1476:                        if (fdef != null
1477:                                && ("eventtime".equals(fdef.getGUIType()) || fdef
1478:                                        .getDataType() instanceof  org.mmbase.datatypes.DateTimeDataType)) { // do something reasonable for this
1479:                            Date date;
1480:                            if (fdef.getType() == Field.TYPE_DATETIME) {
1481:                                date = node.getDateValue(field);
1482:                            } else {
1483:                                date = new Date(node.getLongValue(field) * 1000);
1484:                            }
1485:                            rtn = DateFormat.getDateTimeInstance(
1486:                                    DateFormat.LONG, DateFormat.MEDIUM, locale)
1487:                                    .format(date);
1488:                            Calendar calendar = new GregorianCalendar(locale);
1489:                            calendar.setTime(date);
1490:                            if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
1491:                                java.text.DateFormat df = new java.text.SimpleDateFormat(
1492:                                        " G", locale);
1493:                                rtn += df.format(date);
1494:                            }
1495:                        } else {
1496:                            rtn = (String) pars.get("stringvalue");
1497:                            if (rtn == null) {
1498:                                rtn = node.getStringValue(field);
1499:                            }
1500:                        }
1501:                    }
1502:                    rtn = org.mmbase.util.transformers.Xml.XMLEscape(rtn);
1503:                }
1504:                return rtn;
1505:            }
1506:
1507:            /**
1508:             * Returns a GUI-indicator for the node itself.
1509:             * @since MMBase-1.8.2
1510:             */
1511:            protected String getNodeGUIIndicator(MMObjectNode node,
1512:                    Parameters params) {
1513:                // do the best we can because this method was not implemented
1514:                // we get the first field in the object and try to make it
1515:                // to a string we can return
1516:                List<CoreField> list = getFields(NodeManager.ORDER_LIST);
1517:                if (list.size() > 0) {
1518:                    String fname = list.get(0).getName();
1519:                    String str = node.getStringValue(fname);
1520:                    if (str.length() > 128) {
1521:                        str = str.substring(0, 128) + "...";
1522:                    }
1523:                    if (params == null) {
1524:                        // Needed for getGuiIndicator calls for NODE fields
1525:                        // Temporary fix, should perhaps be solved in getGuiIndicator(node,params)
1526:                        String result = getGUIIndicator(fname, node);
1527:                        if (result == null) {
1528:                            result = str;
1529:                        }
1530:                        return result;
1531:                    } else {
1532:                        params.set("field", fname);
1533:                        params.set("stringvalue", str);
1534:                        return getGUIIndicator(node, params);
1535:                    }
1536:                } else {
1537:                    return GUI_INDICATOR;
1538:                }
1539:            }
1540:
1541:            /**
1542:             * What should a GUI display for this node.
1543:             * Default the value returned is GUI_INDICATOR ('no info').
1544:             * Override this to display your own choice (see Images.java).
1545:             * You may want to override {@link #getNodeGUIIndicator} for more flexibility.
1546:             * @param node The node to display
1547:             * @return the display of the node as a <code>String</code>
1548:             */
1549:            public String getGUIIndicator(MMObjectNode node) {
1550:                return GUI_INDICATOR;
1551:            }
1552:
1553:            /**
1554:             * What should a GUI display for this node/field combo.
1555:             * Default is null (indicating to display the field as is)
1556:             * Override this to display your own choice.
1557:             * @param node The node to display
1558:             * @param fieldName the name field of the field to display
1559:             * @return the display of the node's field as a <code>String</code>, null if not specified
1560:             */
1561:            public String getGUIIndicator(String fieldName, MMObjectNode node) {
1562:                CoreField field = getField(fieldName);
1563:
1564:                if (field != null && field.getType() == Field.TYPE_NODE
1565:                        && !fieldName.equals(FIELD_NUMBER)) {
1566:                    try {
1567:                        MMObjectNode otherNode = node.getNodeValue(fieldName);
1568:                        if (otherNode == null) {
1569:                            return "";
1570:                        } else {
1571:                            // may return GUI_INDICATOR
1572:                            String rtn = otherNode.parent
1573:                                    .getGUIIndicator(otherNode);
1574:                            if (rtn == GUI_INDICATOR) {
1575:                                rtn = otherNode.parent.getNodeGUIIndicator(
1576:                                        otherNode, null);
1577:                            }
1578:                            return rtn;
1579:                        }
1580:                    } catch (RuntimeException rte) {
1581:                        log.warn("Cannot load node from field " + fieldName
1582:                                + " in node " + node.getNumber() + ":" + rte);
1583:                        return "invalid";
1584:                    }
1585:                } else {
1586:                    return null;
1587:                }
1588:            }
1589:
1590:            /**
1591:             * The GUIIndicator can depend on the locale. Override this function
1592:             * @since MMBase-1.6
1593:             */
1594:            protected String getLocaleGUIIndicator(Locale locale, String field,
1595:                    MMObjectNode node) {
1596:                return getGUIIndicator(field, node);
1597:            }
1598:
1599:            /**
1600:             * The GUIIndicator can depend on the locale. Override this function
1601:             * You may want to override {@link #getNodeGUIIndicator} for more flexibility.
1602:             * @since MMBase-1.6
1603:             */
1604:            protected String getLocaleGUIIndicator(Locale locale,
1605:                    MMObjectNode node) {
1606:                return getGUIIndicator(node);
1607:            }
1608:
1609:            /**
1610:             * Gets the field definitions for the editor, sorted according
1611:             * to the specified order, and excluding the fields that have
1612:             * not been assigned a valid position (valid is >= 0).
1613:             * This method makes an explicit sort (it does not use a cached list).
1614:             *
1615:             * @param sortOrder One of the sortorders defined in
1616:             *        {@link org.mmbase.core.CoreField CoreField}
1617:             * @return The ordered list of field definitions.
1618:             */
1619:            public List<CoreField> getFields(int sortOrder) {
1620:                List<CoreField> orderedFields = sortedFieldLists.get(sortOrder);
1621:                if (orderedFields == null) {
1622:                    orderedFields = new ArrayList<CoreField>();
1623:                    for (CoreField field : fields.values()) {
1624:                        if (field.isTemporary()) {
1625:                            continue;
1626:                        }
1627:
1628:                        // include only fields which have been assigned a valid position, and are
1629:                        if ((sortOrder == NodeManager.ORDER_NONE)
1630:                                || ((sortOrder == NodeManager.ORDER_CREATE) && (field
1631:                                        .getStoragePosition() > -1))
1632:                                || ((sortOrder == NodeManager.ORDER_EDIT) && (field
1633:                                        .getEditPosition() > -1))
1634:                                || ((sortOrder == NodeManager.ORDER_SEARCH) && (field
1635:                                        .getSearchPosition() > -1))
1636:                                || ((sortOrder == NodeManager.ORDER_LIST) && (field
1637:                                        .getListPosition() > -1))) {
1638:                            orderedFields.add(field);
1639:                        }
1640:                    }
1641:                    Fields.sort(orderedFields, sortOrder);
1642:                    sortedFieldLists.put(sortOrder, Collections
1643:                            .unmodifiableList(orderedFields));
1644:                } else {
1645:                    //log.info("From cache!");
1646:                }
1647:                return orderedFields;
1648:            }
1649:
1650:            /**
1651:             * Returns the next field as defined by its sortorder, according to the specified order.
1652:             */
1653:            public FieldDefs getNextField(String currentfield, int sortorder) {
1654:                CoreField cdef = getField(currentfield);
1655:                List<CoreField> sortedFields = getFields(sortorder);
1656:                int pos = sortedFields.indexOf(cdef);
1657:                if (pos != -1 && (pos + 1) < sortedFields.size()) {
1658:                    return (FieldDefs) sortedFields.get(pos + 1);
1659:                }
1660:                return null;
1661:            }
1662:
1663:            /**
1664:             * Returns the next field as defined by its sortorder, according to it's GUIPos property (as set in the builder xml file).
1665:             * Used for moving between fields in an edit-form.
1666:             * @deprecated use getNextField() with sortorder ORDER_EDIT
1667:             */
1668:            public FieldDefs getNextField(String currentfield) {
1669:                return getNextField(currentfield, NodeManager.ORDER_EDIT);
1670:            }
1671:
1672:            /**
1673:             * Returns
1674:             * @since MMBase-1.7.4
1675:             */
1676:            protected BlobCache getBlobCache(String fieldName) {
1677:                return genericBlobCache;
1678:            }
1679:
1680:            /**
1681:             * @since MMBase-1.8
1682:             */
1683:            public int clearBlobCache(int nodeNumber) {
1684:                int result = 0;
1685:                for (CoreField field : getFields()) {
1686:                    String fieldName = field.getName();
1687:                    BlobCache cache = getBlobCache(fieldName);
1688:                    String key = cache.getKey(nodeNumber, fieldName);
1689:                    if (cache.remove(key) != null)
1690:                        result++;
1691:                }
1692:                return result;
1693:            }
1694:
1695:            /**
1696:             * Provides additional functionality when obtaining field values.
1697:             * This method is called whenever a Node of the builder's type fails at evaluating a getValue() request
1698:             * (generally when a fieldname is supplied that doesn't exist).
1699:             * It allows the system to add 'functions' to be included with a field name, such as 'html(body)' or 'time(lastmodified)'.
1700:             * This method will parse the fieldname, determining functions and calling the {@link #executeFunction} method to handle it.
1701:             * Functions in fieldnames can be given in the format 'functionname(fieldname)'. An old format allows 'functionname_fieldname' instead,
1702:             * though this only applies to the text functions 'short', 'html', and 'wap'.
1703:             * Functions can be nested, i.e. 'html(shorted(body))'.
1704:             * Derived builders should override this method only if they want to provide virtual fieldnames. To provide additonal functions,
1705:             * call {@link #addFunction} instead. See also the source code for {@link org.mmbase.util.functions.ExampleBuilder}.
1706:             * @param node the node whos efields are queries
1707:             * @param field the fieldname that is requested
1708:             * @return the result of the 'function', or null if no valid functions could be determined.
1709:             */
1710:            public Object getValue(MMObjectNode node, String field) {
1711:                Object rtn = getObjectValue(node, field);
1712:
1713:                // Old code
1714:                if (field.indexOf("short_") == 0) {
1715:                    String val = node.getStringValue(field.substring(6));
1716:                    val = getShort(val, 34);
1717:                    rtn = val;
1718:                } else if (field.indexOf("html_") == 0) {
1719:                    String val = node.getStringValue(field.substring(5));
1720:                    val = getHTML(val);
1721:                    rtn = val;
1722:                } else if (field.indexOf("wap_") == 0) {
1723:                    String val = node.getStringValue(field.substring(4));
1724:                    val = getWAP(val);
1725:                    rtn = val;
1726:                }
1727:                // end old
1728:                return rtn;
1729:            }
1730:
1731:            /**
1732:             * Like getValue, but without the 'old' code (short_ html_ etc). This is for
1733:             * protected use, when you are sure this is not used, and you can
1734:             * avoid the overhead.
1735:             *
1736:             * @since MMBase-1.6
1737:             * @see #getValue
1738:             */
1739:
1740:            protected Object getObjectValue(MMObjectNode node, String field) {
1741:                Object rtn = null;
1742:                int pos1 = field.indexOf('(');
1743:                if (pos1 != -1) {
1744:                    int pos2 = field.lastIndexOf(')');
1745:                    if (pos2 != -1) {
1746:                        String name = field.substring(pos1 + 1, pos2);
1747:                        String function = field.substring(0, pos1);
1748:                        if (log.isDebugEnabled()) {
1749:                            log.debug("function = '" + function
1750:                                    + "', fieldname = '" + name + "'");
1751:                        }
1752:                        List<String> a = new ArrayList<String>();
1753:                        a.add(name);
1754:                        rtn = getFunctionValue(node, function, a);
1755:
1756:                    }
1757:                }
1758:                return rtn;
1759:            }
1760:
1761:            /**
1762:             * Parses string containing function parameters.
1763:             * The parameters must be separated by ',' or ';' and may be functions
1764:             * themselves (i.e. a functionname, followed by a parameter list between
1765:             * parenthesis).
1766:             *
1767:             * @param fields The string, containing function parameters.
1768:             * @return List of function parameters (may be functions themselves).
1769:             * @deprecated use executeFunction(node, function, list)
1770:             */
1771:            protected Vector<String> getFunctionParameters(String fields) {
1772:                int commapos = 0;
1773:                int nested = 0;
1774:                Vector<String> v = new Vector<String>();
1775:                int i;
1776:                if (log.isDebugEnabled())
1777:                    log.debug("Fields=" + fields);
1778:                for (i = 0; i < fields.length(); i++) {
1779:                    if ((fields.charAt(i) == ',') || (fields.charAt(i) == ';')) {
1780:                        if (nested == 0) {
1781:                            v.add(fields.substring(commapos, i).trim());
1782:                            commapos = i + 1;
1783:                        }
1784:                    }
1785:                    if (fields.charAt(i) == '(') {
1786:                        nested++;
1787:                    }
1788:                    if (fields.charAt(i) == ')') {
1789:                        nested--;
1790:                    }
1791:                }
1792:                if (i > 0) {
1793:                    v.add(fields.substring(commapos).trim());
1794:                }
1795:                return v;
1796:            }
1797:
1798:            /**
1799:             * Executes a 'function' on a MMObjectNode. The function is
1800:             * identified by a string, and its arguments are passed by a List.
1801:             *
1802:             * The function 'info' should exist, and this will return a Map
1803:             * with descriptions of the possible functions.
1804:             *
1805:             * Call {@link #addFunction} in your extension if you want to add functions.
1806:             *
1807:             * @param node The node on which the function must be executed
1808:             * @param functionName The string identifying the funcion
1809:             * @param parameters The list with function argument or null (which means 'no arguments')
1810:             *
1811:             * @see #executeFunction
1812:             * @since MMBase-1.6
1813:             */
1814:            // package because called from MMObjectNode
1815:            final Object getFunctionValue(MMObjectNode node,
1816:                    String functionName, List<?> parameters) {
1817:                if (parameters == null)
1818:                    parameters = new ArrayList<String>();
1819:                // for backwards compatibility (calling with string function with more than one argument)
1820:                if (parameters.size() == 1
1821:                        && parameters.get(0) instanceof  String) {
1822:                    String arg = (String) parameters.get(0);
1823:                    Object result = executeFunction(node, functionName, arg);
1824:                    if (result != null) {
1825:                        return result;
1826:                    }
1827:                    parameters = StringSplitter.splitFunctions(arg);
1828:                }
1829:                Function<?> function = getFunction(node, functionName);
1830:                if (function != null) {
1831:                    return function.getFunctionValueWithList(parameters);
1832:                } else {
1833:                    // fallback
1834:                    return executeFunction(node, functionName, parameters);
1835:                }
1836:            }
1837:
1838:            /**
1839:             * Instantiates a Function object for a certain function on a certain node of this type.
1840:             * @param node The Node for on which the function must work
1841:             * @param functionName Name of the request function.
1842:             * @return a Function object or <code>null</code> if no such function.
1843:             * @since MMBase-1.8
1844:             */
1845:            protected Function<?> getFunction(MMObjectNode node,
1846:                    String functionName) {
1847:                Function<?> function = getFunction(functionName);
1848:                if (function instanceof  NodeFunction) {
1849:                    return ((NodeFunction<?>) function).newInstance(node);
1850:                } else {
1851:                    return null;
1852:                }
1853:            }
1854:
1855:            /**
1856:             * Returns all Functions which are available (or at least known to be available) on a Node.
1857:             * @since MMBase-1.8
1858:             */
1859:            protected Collection<Function<?>> getFunctions(MMObjectNode node) {
1860:                Collection<Function<?>> nodeFunctions = new HashSet<Function<?>>();
1861:                for (Function<?> function : getFunctions()) {
1862:                    if (function instanceof  NodeFunction) {
1863:                        nodeFunctions.add(((NodeFunction<?>) function)
1864:                                .newInstance(node));
1865:                    }
1866:                }
1867:                return nodeFunctions;
1868:            }
1869:
1870:            /**
1871:             *
1872:             * @inheritDoc
1873:             * @since MMBase-1.8
1874:             */
1875:            protected Function newFunctionInstance(String name,
1876:                    Parameter[] parameters, ReturnType returnType) {
1877:                return new NodeFunction<Object>(name, parameters, returnType) {
1878:                    public Object getFunctionValue(Node node,
1879:                            Parameters parameters) {
1880:                        return MMObjectBuilder.this .executeFunction(
1881:                                getCoreNode(MMObjectBuilder.this , node), name,
1882:                                parameters.subList(0, parameters.size() - 1) // removes the node-argument, some legacy impl. get confused
1883:                                );
1884:                    }
1885:                };
1886:            }
1887:
1888:            /**
1889:             * Executes a function on the field of a node, and returns the result.
1890:             * This method is called by the builder's {@link #getValue} method.
1891:             * Derived builders should override this method to provide additional functions.
1892:             *
1893:             * @since MMBase-1.6
1894:             * @throws IllegalArgumentException if the argument List does not
1895:             * fit the function
1896:             * @see #executeFunction
1897:             */
1898:            protected Object executeFunction(MMObjectNode node,
1899:                    String function, List<?> arguments) {
1900:                if (log.isDebugEnabled()) {
1901:                    log.debug("Executing function " + function + " on node "
1902:                            + node.getNumber() + " with argument " + arguments);
1903:                }
1904:
1905:                if (function.equals("info")) {
1906:                    Map<String, String> info = new HashMap<String, String>();
1907:                    for (Function<?> f : getFunctions(node)) {
1908:                        info.put(f.getName(), f.getDescription());
1909:                    }
1910:                    info
1911:                            .put(
1912:                                    "info",
1913:                                    "(functionname) Returns information about a certain 'function'. Or a map of all function if no arguments.");
1914:                    if (arguments == null || arguments.size() == 0
1915:                            || arguments.get(0) == null) {
1916:                        log.info("returing " + info);
1917:                        return info;
1918:                    } else {
1919:                        return info.get(arguments.get(0));
1920:                    }
1921:                } else if (function.equals("wrap")) {
1922:                    if (arguments.size() < 2)
1923:                        throw new IllegalArgumentException(
1924:                                "wrap function needs 2 arguments (currently:"
1925:                                        + arguments.size() + " : " + arguments
1926:                                        + ")");
1927:                    try {
1928:                        String val = node.getStringValue((String) arguments
1929:                                .get(0));
1930:                        int wrappos = Integer.parseInt((String) arguments
1931:                                .get(1));
1932:                        return wrap(val, wrappos);
1933:                    } catch (Exception e) {
1934:                    }
1935:
1936:                } else if (function.equals("substring")) {
1937:                    if (arguments.size() < 2)
1938:                        throw new IllegalArgumentException(
1939:                                "substring function needs 2 or 3 arguments (currently:"
1940:                                        + arguments.size() + " : " + arguments
1941:                                        + ")");
1942:                    try {
1943:                        String val = node.getStringValue((String) arguments
1944:                                .get(0));
1945:                        int len = Integer.parseInt((String) arguments.get(1));
1946:                        if (arguments.size() > 2) {
1947:                            String filler = (String) arguments.get(2);
1948:                            return substring(val, len, filler);
1949:                        } else {
1950:                            return substring(val, len, null);
1951:                        }
1952:                    } catch (Exception e) {
1953:                        log.debug(Logging.stackTrace(e));
1954:                        return e.toString();
1955:                    }
1956:                }
1957:
1958:                String field = "";
1959:                if (arguments != null && arguments.size() > 0) {
1960:                    Object o = arguments.get(0);
1961:                    if (o instanceof  String) {
1962:                        field = (String) o;
1963:                    }
1964:                }
1965:
1966:                // time functions
1967:                if (function.equals("date")) { // date
1968:                    int v = node.getIntValue(field);
1969:                    return DateSupport.date2string(v);
1970:                } else if (function.equals("time")) { // time hh:mm
1971:                    int v = node.getIntValue(field);
1972:                    return DateSupport.getTime(v);
1973:                } else if (function.equals("timesec")) { // timesec hh:mm:ss
1974:                    int v = node.getIntValue(field);
1975:                    return DateSupport.getTimeSec(v);
1976:                } else if (function.equals("longmonth")) { // longmonth September
1977:                    int v = node.getIntValue(field);
1978:                    return DateStrings.ENGLISH_DATESTRINGS.getMonth(DateSupport
1979:                            .getMonthInt(v));
1980:                } else if (function.equals("monthnumber")) {
1981:                    int v = node.getIntValue(field);
1982:                    return "" + (DateSupport.getMonthInt(v) + 1);
1983:                } else if (function.equals("month")) { // month Sep
1984:                    int v = node.getIntValue(field);
1985:                    return DateStrings.DUTCH_DATESTRINGS
1986:                            .getShortMonth(DateSupport.getMonthInt(v));
1987:                } else if (function.equals("weekday")) { // weekday Sunday
1988:                    int v = node.getIntValue(field);
1989:                    return DateStrings.DUTCH_DATESTRINGS.getDay(DateSupport
1990:                            .getWeekDayInt(v));
1991:                } else if (function.equals("shortday")) { // shortday Sun
1992:                    int v = node.getIntValue(field);
1993:                    return DateStrings.DUTCH_DATESTRINGS
1994:                            .getShortDay(DateSupport.getWeekDayInt(v));
1995:                } else if (function.equals("day")) { // day 4
1996:                    int v = node.getIntValue(field);
1997:                    return "" + DateSupport.getDayInt(v);
1998:                } else if (function.equals("shortyear")) { // year 01
1999:                    int v = node.getIntValue(field);
2000:                    return (DateSupport.getYear(v)).substring(2);
2001:                } else if (function.equals("year")) { // year 2001
2002:                    int v = node.getIntValue(field);
2003:                    return DateSupport.getYear(v);
2004:                } else if (function.equals("thisdaycurtime")) { //
2005:                    int curtime = node.getIntValue(field);
2006:                    // gives us the next full day based on time (00:00)
2007:                    int days = curtime / (3600 * 24);
2008:                    return "" + ((days * (3600 * 24)) - 3600);
2009:                } else if (function.equals("age")) {
2010:                    Integer val = Integer.valueOf(node.getAge());
2011:                    return val.toString();
2012:                } else if (function.equals("wap")) {
2013:                    String val = node.getStringValue(field);
2014:                    return getWAP(val);
2015:                } else if (function.equals("html")) {
2016:                    String val = node.getStringValue(field);
2017:                    return getHTML(val);
2018:                } else if (function.equals("shorted")) {
2019:                    String val = node.getStringValue(field);
2020:                    return getShort(val, 32);
2021:                } else if (function.equals("uppercase")) {
2022:                    String val = node.getStringValue(field);
2023:                    return val.toUpperCase();
2024:                } else if (function.equals("lowercase")) {
2025:                    String val = node.getStringValue(field);
2026:                    return val.toLowerCase();
2027:                } else if (function.equals("hostname")) {
2028:                    String val = node.getStringValue(field);
2029:                    return hostname_function(val);
2030:                } else if (function.equals("urlencode")) {
2031:                    String val = node.getStringValue(field);
2032:                    return getURLEncode(val);
2033:                } else if (function.startsWith("wrap_")) {
2034:                    String val = node.getStringValue(field);
2035:                    try {
2036:                        int wrappos = Integer.parseInt(function.substring(5));
2037:                        return wrap(val, wrappos);
2038:                    } catch (Exception e) {
2039:                    }
2040:                } else if (function.equals("currency_euro")) {
2041:                    double val = node.getDoubleValue(field);
2042:                    NumberFormat nf = NumberFormat
2043:                            .getNumberInstance(Locale.GERMANY);
2044:                    return "" + nf.format(val);
2045:                } else {
2046:                    StringBuilder arg = new StringBuilder(field);
2047:                    if (arguments != null) {
2048:                        for (int i = 1; i < arguments.size(); i++) {
2049:                            if (arg.length() > 0)
2050:                                arg.append(',');
2051:                            arg.append(arguments.get(i));
2052:                        }
2053:                    }
2054:                    return executeFunction(node, function, arg.toString());
2055:                }
2056:                return null;
2057:            }
2058:
2059:            /**
2060:             * Executes a function on the field of a node, and returns the result.
2061:             * This method is called by the builder's {@link #getValue} method.
2062:             *
2063:             * current functions are:<br />
2064:             * on dates: date, time, timesec, longmonth, month, monthnumber, weekday, shortday, day, yearhort year<br />
2065:             * on text:  wap, html, shorted, uppercase, lowercase <br />
2066:             * on node:  age() <br />
2067:             * on numbers: wrap_&lt;int&gt;, currency_euro <br />
2068:             *
2069:             * @param node the node whose fields are queries
2070:             * @param field the fieldname that is requested
2071:             * @return the result of the 'function', or null if no valid functions could be determined.
2072:             * @deprecated use {@link #getFunction(MMObjectNode, String)}
2073:             */
2074:            protected Object executeFunction(MMObjectNode node,
2075:                    String function, String field) {
2076:                if (log.isDebugEnabled()) {
2077:                    log.debug("Executing function " + function + " on node "
2078:                            + node.getNumber() + " with argument " + field);
2079:                }
2080:                return null;
2081:            }
2082:
2083:            /**
2084:             * Returns all relations of a node.
2085:             * This returns the relation objects, not the objects related to.
2086:             * Note that the relations returned are always of builder type 'InsRel', even if they are really from a derived builser such as AuthRel.
2087:             * @param src the number of the node to obtain the relations from
2088:             * @return a <code>Vector</code> with InsRel nodes
2089:             * @todo Return-type and name of this function are not sound.
2090:             */
2091:            public Vector<MMObjectNode> getRelations_main(int src) {
2092:                InsRel bul = mmb.getInsRel();
2093:                if (bul == null) {
2094:                    log.error("getMMObject(): InsRel not yet loaded");
2095:                    return null;
2096:                }
2097:                return bul.getRelationsVector(src);
2098:            }
2099:
2100:            /**
2101:             * Return the default url of this object.
2102:             * The basic value returned is <code>null</code>.
2103:             * @param src the number of the node to obtain the url from
2104:             * @return the basic url as a <code>String</code>, or <code>null</code> if unknown.
2105:             */
2106:            public String getDefaultUrl(int src) {
2107:                return null;
2108:            }
2109:
2110:            /**
2111:             * @deprecated This method will be finalized in MMBase 1.9 and removed afterwards.
2112:             *
2113:             * You can implement a new smart-path for your builders, with a class like {@link
2114:             * org.mmbase.module.core.SmartPathFunction} in stead, and configure it in your builder xml as
2115:             * the implementation for the 'smartpath' function. This makes extensions less dependent on
2116:             * precise arguments (e.g. 'documentRoot' is not relevant for 'resourceloader' implementation),
2117:             * and makes this function pluggable on all builders. See also  MMB-1449.
2118:             *
2119:             */
2120:            public String getSmartPath(String documentRoot, String path,
2121:                    String nodeNumber, String version) {
2122:                if (log.isDebugEnabled()) {
2123:                    log.debug("Getting smartpath for " + documentRoot + " /"
2124:                            + path + "/" + nodeNumber + "/" + version);
2125:                }
2126:                File dir = new File(documentRoot + path);
2127:                if (version != null)
2128:                    nodeNumber += "." + version;
2129:                String[] matches = dir.list(new SPartFileFilter(nodeNumber));
2130:                if ((matches == null) || (matches.length == 0)) {
2131:                    return null;
2132:                }
2133:                return path + matches[0] + File.separator;
2134:            }
2135:
2136:            /**
2137:             * Get the name of this mmserver from the MMBase Root
2138:             * @return a <code>String</code> which is the server's name
2139:             */
2140:            public String getMachineName() {
2141:                return mmb.getMachineName();
2142:            }
2143:
2144:            /**
2145:             * Called when a remote node is changed.
2146:             * Should be called by subclasses if they override it.
2147:             * @param machine Name of the machine that changed the node.
2148:             * @param number Number of the changed node as a <code>String</code>
2149:             * @param builder type of the changed node
2150:             * @param ctype command type, 'c'=changed, 'd'=deleted', 'r'=relations changed, 'n'=new
2151:             * @return always <code>true</code>
2152:             * @deprecated use notify(NodeEvent) in stead
2153:             */
2154:            public boolean nodeRemoteChanged(String machine, String number,
2155:                    String builder, String ctype) {
2156:                // signal all the other objects that have shown interest in changes of nodes of this builder type.
2157:                for (MMBaseObserver o : remoteObservers) {
2158:                    if (o != this ) {
2159:                        o.nodeRemoteChanged(machine, number, builder, ctype);
2160:                    } else {
2161:                        log.warn(getClass().getName() + " " + toString()
2162:                                + " observes itself");
2163:                    }
2164:                }
2165:                return true;
2166:            }
2167:
2168:            /**
2169:             * Called when a local node is changed.
2170:             * Should be called by subclasses if they override it.
2171:             * @param machine Name of the machine that changed the node.
2172:             * @param number Number of the changed node as a <code>String</code>
2173:             * @param builder type of the changed node
2174:             * @param ctype command type, 'c'=changed, 'd'=deleted', 'r'=relations changed, 'n'=new
2175:             * @return always <code>true</code>
2176:             * @deprecated use notify(NodeEvent) in stead
2177:             */
2178:
2179:            public boolean nodeLocalChanged(String machine, String number,
2180:                    String builder, String ctype) {
2181:                // signal all the other objects that have shown interest in changes of nodes of this builder type.
2182:                synchronized (localObservers) {
2183:                    for (MMBaseObserver o : localObservers) {
2184:                        if (o != this ) {
2185:                            o.nodeLocalChanged(machine, number, builder, ctype);
2186:                        } else {
2187:                            log.warn(getClass().getName() + " " + toString()
2188:                                    + " observes itself");
2189:                        }
2190:                    }
2191:                }
2192:
2193:                return true;
2194:            }
2195:
2196:            /**
2197:             * Called when a local field is changed.
2198:             * @param number Number of the changed node as a <code>String</code>
2199:             * @param builder type of the changed node
2200:             * @param field name of the changed field
2201:             * @param value value it changed to
2202:             * @return always <code>true</code>
2203:             */
2204:            public boolean fieldLocalChanged(String number, String builder,
2205:                    String field, String value) {
2206:                if (log.isDebugEnabled()) {
2207:                    log.debug("FLC=" + number + " BUL=" + builder + " FIELD="
2208:                            + field + " value=" + value);
2209:                }
2210:                return true;
2211:            }
2212:
2213:            /**
2214:             * Adds a remote observer to this builder.
2215:             * The observer is notified whenever an object of this builder is changed, added, or removed.
2216:             * @return always <code>true</code>
2217:             * @deprecated use the new event system as well. check out addEventListener(Object listener) or MMBase.addEventListener(EventListener listener)
2218:             */
2219:            public boolean addRemoteObserver(MMBaseObserver obs) {
2220:                if (!remoteObservers.contains(obs)) {
2221:                    remoteObservers.add(obs);
2222:                }
2223:                return true;
2224:            }
2225:
2226:            /**
2227:             * Adds a local observer to this builder.
2228:             * The observer is notified whenever an object of this builder is changed, added, or removed.
2229:             * @return always <code>true</code>
2230:             * @deprecated use the new event system as well. check out addEventListener(Object listener) or MMBase.addEventListener(EventListener listener)
2231:             */
2232:            public boolean addLocalObserver(MMBaseObserver obs) {
2233:                if (!localObservers.contains(obs)) {
2234:                    localObservers.add(obs);
2235:                }
2236:                return true;
2237:            }
2238:
2239:            /**
2240:             * @since MMBase-1.8
2241:             */
2242:            public boolean removeLocalObserver(MMBaseObserver obs) {
2243:                return localObservers.remove(obs);
2244:            }
2245:
2246:            /**
2247:             * @since MMBase-1.8
2248:             */
2249:            public boolean removeRemoteObserver(MMBaseObserver obs) {
2250:                return remoteObservers.remove(obs);
2251:            }
2252:
2253:            /**
2254:             *  Used to create a default teaser by any builder
2255:             *  @deprecated Will be removed?
2256:             */
2257:            public MMObjectNode getDefaultTeaser(MMObjectNode node,
2258:                    MMObjectNode tnode) {
2259:                log
2260:                        .warn("getDefaultTeaser(): Generate Teaser,Should be overridden");
2261:                return tnode;
2262:            }
2263:
2264:            /**
2265:             * Waits until a node is changed (multicast).
2266:             * @param node the node to wait for
2267:             */
2268:            /*
2269:            public boolean waitUntilNodeChanged(MMObjectNode node) {
2270:                return mmb.mmc.waitUntilNodeChanged(node);
2271:            }
2272:             */
2273:
2274:            /**
2275:             * Obtains a list of string values by performing the provided command and parameters.
2276:             * This method is SCAN related and may fail if called outside the context of the SCAN servlet.
2277:             * @param sp The PageInfo (containing http and user info) that calls the function
2278:             * @param tagger a Hashtable of parameters (name-value pairs) for the command
2279:             * @param tok a list of strings that describe the (sub)command to execute
2280:             * @return a <code>Vector</code> containing the result values as a <code>String</code>
2281:             */
2282:            public Vector<String> getList(PageInfo sp, StringTagger tagger,
2283:                    StringTokenizer tok) {
2284:                throw new UnsupportedOperationException(
2285:                        getClass().getName()
2286:                                + " should override the getList method (you've probably made a typo)");
2287:            }
2288:
2289:            /**
2290:             * Obtains a string value by performing the provided command.
2291:             * The command can be called:
2292:             * <ul>
2293:             *   <li>by SCAN : $MOD-MMBASE-BUILDER-[buildername]-[command]</li>
2294:             *   <li>in jsp : cloud.getNodeManager(buildername).getInfo(command);</li>
2295:             * </lu>
2296:             * This method is SCAN related and some commands may fail if called outside the context of the SCAN servlet.
2297:             * @param sp The PageInfo (containing http and user info) that calls the function
2298:             * @param tok a list of strings that describe the (sub)command to execute
2299:             * @return the result value as a <code>String</code>
2300:             */
2301:            public String replace(PageInfo sp, StringTokenizer tok) {
2302:                log.warn("replace(): replace called should be overridden");
2303:                return "";
2304:            }
2305:
2306:            /**
2307:             * The hook that passes all form related pages to the correct handler.
2308:             * This method is SCAN related and may fail if called outside the context of the SCAN servlet.
2309:             * The methood is currentkly called by the MMEDIT module, whenever a 'PRC-CMD-BUILDER-...' command
2310:             * is encountered in the list of commands to be processed.
2311:             * @param sp The PageInfo (containing http and user info) that calls the function
2312:             * @param command a list of strings that describe the (sub)command to execute (the portion after ' PRC-CMD-BUILDER')
2313:             * @param cmds the commands (PRC-CMD) that are iurrently being processed, including the current command.
2314:             * @param vars variables (PRC-VAR) thatw ere set to be used during processing.
2315:             * @return the result value as a <code>String</code>
2316:             */
2317:            public boolean process(PageInfo sp, StringTokenizer command,
2318:                    Hashtable cmds, Hashtable vars) {
2319:                return false;
2320:            }
2321:
2322:            /**
2323:             * Set description of the builder
2324:             * @param e the description text
2325:             */
2326:            public void setDescription(String e) {
2327:                this .description = e;
2328:                update();
2329:            }
2330:
2331:            /**
2332:             * Set descriptions of the builder
2333:             * @param e a <code>Hashtable</code> containing the descriptions
2334:             */
2335:            public void setDescriptions(Hashtable<String, String> e) {
2336:                this .descriptions = e;
2337:                update();
2338:            }
2339:
2340:            /**
2341:             * Get description of the builder
2342:             * @return the description text
2343:             */
2344:            public String getDescription() {
2345:                return description;
2346:            }
2347:
2348:            /**
2349:             * Gets description of the builder, using the specified language.
2350:             * @param lang The language requested
2351:             * @return the descriptions in that language, or <code>null</code> if it is not avaialble
2352:             */
2353:            public String getDescription(String lang) {
2354:                if (descriptions == null)
2355:                    return null;
2356:                String retval = descriptions.get(lang);
2357:                if (retval == null) {
2358:                    return getDescription();
2359:                }
2360:                return retval;
2361:            }
2362:
2363:            /**
2364:             * Get descriptions of the builder
2365:             * @return  a <code>Hashtable</code> containing the descriptions
2366:             */
2367:            public Hashtable<String, String> getDescriptions() {
2368:                return descriptions;
2369:            }
2370:
2371:            /**
2372:             * Sets search Age.
2373:             * @param age the search age as a <code>String</code>
2374:             */
2375:            public void setSearchAge(String age) {
2376:                this .searchAge = age;
2377:                update();
2378:            }
2379:
2380:            /**
2381:             * Gets search Age
2382:             * @return the search age as a <code>String</code>
2383:             */
2384:            public String getSearchAge() {
2385:                return searchAge;
2386:            }
2387:
2388:            /**
2389:             * Gets short name of the builder, using the specified language.
2390:             * @param lang The language requested
2391:             * @return the short name in that language, or <code>null</code> if it is not available
2392:             */
2393:            public String getSingularName(String lang) {
2394:                String tmp = null;
2395:                if (singularNames != null) {
2396:                    tmp = singularNames.get(lang);
2397:                    if (tmp == null)
2398:                        tmp = singularNames.get(mmb.getLanguage());
2399:                    if (tmp == null)
2400:                        tmp = singularNames.get("en");
2401:                }
2402:                if (tmp == null)
2403:                    tmp = tableName;
2404:                return tmp;
2405:            }
2406:
2407:            /**
2408:             * Gets short name of the builder in the current default language.
2409:             * If the current language is not available, the "en" version is returned instead.
2410:             * If that name is not available, the internal builder name (table name) is returned.
2411:             * @return the short name in either the default language or in "en"
2412:             */
2413:            public String getSingularName() {
2414:                return getSingularName(mmb.getLanguage());
2415:            }
2416:
2417:            /**
2418:             * Gets long name of the builder, using the specified language.
2419:             * @param lang The language requested
2420:             * @return the long name in that language, or <code>null</code> if it is not available
2421:             */
2422:            public String getPluralName(String lang) {
2423:                String tmp = null;
2424:                if (pluralNames != null) {
2425:                    tmp = pluralNames.get(lang);
2426:                    if (tmp == null)
2427:                        tmp = pluralNames.get(mmb.getLanguage());
2428:                    if (tmp == null)
2429:                        tmp = pluralNames.get("en");
2430:                    if (tmp == null)
2431:                        tmp = getSingularName(lang);
2432:                }
2433:                if (tmp == null)
2434:                    tmp = tableName;
2435:                return tmp;
2436:            }
2437:
2438:            /**
2439:             * Gets long name of the builder in the current default language.
2440:             * If the current language is not available, the "en" version is returned instead.
2441:             * If that name is not available, the singular name is returned.
2442:             * @return the long name in either the default language or in "en"
2443:             */
2444:            public String getPluralName() {
2445:                return getPluralName(mmb.getLanguage());
2446:            }
2447:
2448:            /**
2449:             * Returns the classname of this builder
2450:             * @deprecated don't use
2451:             */
2452:            public String getClassName() {
2453:                return this .getClass().getName();
2454:            }
2455:
2456:            /**
2457:             * Send a signal to other servers that a field was changed.
2458:             * @param node the node the field was changed in
2459:             * @param fieldName the name of the field that was changed
2460:             * @return always <code>true</code>
2461:             */
2462:            public boolean sendFieldChangeSignal(MMObjectNode node,
2463:                    String fieldName) {
2464:                // we need to find out what the DBState is of this field so we know
2465:                // who to notify of this change
2466:                int state = getDBState(fieldName);
2467:                log.debug("Changed field=" + fieldName + " dbstate=" + state);
2468:
2469:                // still a large hack need to figure out remote changes
2470:                if (state == 0) {
2471:                }
2472:                // convert the field to a string
2473:
2474:                int type = getDBType(fieldName);
2475:                String value = "";
2476:                if ((type == Field.TYPE_INTEGER) || (type == Field.TYPE_NODE)) {
2477:                    value = "" + node.getIntValue(fieldName);
2478:                } else if (type == Field.TYPE_STRING) {
2479:                    value = node.getStringValue(fieldName);
2480:                } else {
2481:                    // should be mapped to the builder
2482:                }
2483:
2484:                fieldLocalChanged("" + node.getNumber(), tableName, fieldName,
2485:                        value);
2486:                //mmb.mmc.changedNode(node.getNumber(),tableName,"f");
2487:                return true;
2488:            }
2489:
2490:            /**
2491:             * Send a signal to other servers that a new node was created.
2492:             * @param tableName the table in which a node was edited (?)
2493:             * @param number the number of the new node
2494:             * @return always <code>true</code>
2495:             */
2496:            /*
2497:            public boolean signalNewObject(String tableName,int number) {
2498:                if (mmb.mmc!=null) {
2499:                    mmb.mmc.changedNode(number,tableName,"n");
2500:                }
2501:                return true;
2502:            }
2503:             */
2504:
2505:            /**
2506:             * Sets a list of singular names (language - value pairs)
2507:             */
2508:            public void setSingularNames(Hashtable<String, String> names) {
2509:                singularNames = names;
2510:                update();
2511:            }
2512:
2513:            /**
2514:             * Gets a list of singular names (language - value pairs)
2515:             */
2516:            public Hashtable<String, String> getSingularNames() {
2517:                return singularNames;
2518:            }
2519:
2520:            /**
2521:             * Sets a list of plural names (language - value pairs)
2522:             */
2523:            public void setPluralNames(Hashtable<String, String> names) {
2524:                pluralNames = names;
2525:                update();
2526:            }
2527:
2528:            /**
2529:             * Gets a list of plural names (language - value pairs)
2530:             */
2531:            public Hashtable<String, String> getPluralNames() {
2532:                return pluralNames;
2533:            }
2534:
2535:            /**
2536:             * Get text from a blob field. This function is called to 'load' a field into the node, because
2537:             * it was not loaded together with the node, because it is supposed to be too big.
2538:             * @param fieldName name of the field
2539:             * @param node
2540:             * @return a <code>String</code> containing the complate contents of a field as text.
2541:             * @since MMBase-1.8
2542:             */
2543:            protected String getShortedText(String fieldName, MMObjectNode node) {
2544:                if (node.getNumber() < 0)
2545:                    return null; // capture calls from temporary nodes
2546:                try {
2547:                    return mmb.getStorageManager().getStringValue(node,
2548:                            getField(fieldName));
2549:                } catch (StorageException se) {
2550:                    log.error(se.getMessage());
2551:                    log.error(Logging.stackTrace(se));
2552:                    return null;
2553:                }
2554:            }
2555:
2556:            /**
2557:             * Get binary data of a blob field. This function is called to 'load' a field into the node, because
2558:             * it was not loaded together with the node, because it is supposed to be too big.
2559:             * @param fieldName name of the field
2560:             * @param node
2561:             * @return an array of <code>byte</code> containing the complete contents of the field.
2562:             * @since MMBase-1.8
2563:             */
2564:            protected byte[] getShortedByte(String fieldName, MMObjectNode node) {
2565:                if (node.getNumber() < 0)
2566:                    return null; // capture calls from temporary nodes
2567:                try {
2568:                    return mmb.getStorageManager().getBinaryValue(node,
2569:                            getField(fieldName));
2570:                } catch (StorageException se) {
2571:                    log.error(se.getMessage());
2572:                    log.error(Logging.stackTrace(se));
2573:                    return null;
2574:                }
2575:            }
2576:
2577:            /**
2578:             *  Sets a key/value pair in the main values of this node.
2579:             *  Note that if this node is a node in cache, the changes are immediately visible to
2580:             *  everyone, even if the changes are not committed.
2581:             *  The fieldname is added to the (public) 'changed' vector to track changes.
2582:             *  @param fieldName the name of the field to change
2583:             *  @param node      The node on which to change the field (the new value is in this node)
2584:             *  @param originalValue the value which was original in the field
2585:             *  @return <code>true</code> When an update is required(when changed),
2586:             *    <code>false</code> if original value was set back into the field.
2587:             */
2588:            public boolean setValue(MMObjectNode node, String fieldName,
2589:                    Object originalValue) {
2590:                return setValue(node, fieldName);
2591:            }
2592:
2593:            /**
2594:             * Provides additional functionality when setting field values.
2595:             * This method is called whenever a Node of the builder's type tries to change a value.
2596:             * It allows the system to add functionality such as checking valid data.
2597:             * Derived builders should override this method if they want to add functionality.
2598:             * @param node the node whose fields are changed
2599:             * @param fieldName the fieldname that is changed
2600:             * @return <code>true</code> if the call was handled.
2601:             */
2602:            public boolean setValue(MMObjectNode node, String fieldName) {
2603:                return true;
2604:            }
2605:
2606:            /**
2607:             * Returns a HTML-version of a string.
2608:             * This replaces a number of tokens with HTML sequences.
2609:             * The default output does not match well with the new xhtml standards (ugly html), nor does it replace all tokens.
2610:             *
2611:             * Default replacements can be overridden by setting the builder properties in your <builder>.xml:
2612:             *
2613:             * html.alinea
2614:             * html.endofline
2615:             *
2616:             * Example:
2617:             * <properties>
2618:             *   <property name="html.alinea"> &lt;br /&gt; &lt;br /&gt;</property>
2619:             *   <property name="html.endofline"> &lt;br /&gt; </property>
2620:             * </properties>
2621:             *
2622:             * @param body text to convert
2623:             * @return the convert text
2624:             * @deprecated
2625:             */
2626:
2627:            protected String getHTML(String body) {
2628:                String rtn = "";
2629:                if (body != null) {
2630:                    StringObject obj = new StringObject(body);
2631:                    // escape ampersand first
2632:                    obj.replace("&", "&amp;");
2633:
2634:                    obj.replace("<", "&lt;");
2635:                    obj.replace(">", "&gt;");
2636:                    // escape dollar-sign (prevent SCAN code to be run)
2637:                    obj.replace("$", "&#36;");
2638:                    // unquote ampersand and quotes (see escapeXML method)
2639:                    obj.replace("\"", "&quot;");
2640:                    obj.replace("'", "&#39;");
2641:
2642:                    String alinea = getInitParameter("html.alinea");
2643:                    String endofline = getInitParameter("html.endofline");
2644:
2645:                    if (alinea != null) {
2646:                        obj.replace("\r\n\r\n", alinea);
2647:                        obj.replace("\n\n", alinea);
2648:                    } else {
2649:                        obj.replace("\r\n\r\n", DEFAULT_ALINEA);
2650:                        obj.replace("\n\n", DEFAULT_ALINEA);
2651:                    }
2652:
2653:                    if (endofline != null) {
2654:                        obj.replace("\r\n", endofline);
2655:                        obj.replace("\n", endofline);
2656:                    } else {
2657:                        obj.replace("\r\n", DEFAULT_EOL);
2658:                        obj.replace("\n", DEFAULT_EOL);
2659:                    }
2660:
2661:                    rtn = obj.toString();
2662:                }
2663:                return rtn;
2664:            }
2665:
2666:            /**
2667:             * Returns a WAP-version of a string.
2668:             * This replaces a number of tokens with WAP sequences.
2669:             * @param body text to convert
2670:             * @return the convert text
2671:             */
2672:            protected static String getWAP(String body) {
2673:                String result = "";
2674:                if (body != null) {
2675:                    StringObject obj = new StringObject(body);
2676:                    obj.replace("\"", "&#34;");
2677:                    obj.replace("&", "&#38;#38;");
2678:                    obj.replace("'", "&#39;");
2679:                    obj.replace("<", "&#38;#60;");
2680:                    obj.replace(">", "&#62;");
2681:                    result = obj.toString();
2682:                }
2683:                return result;
2684:            }
2685:
2686:            /**
2687:             * Returns a URLEncoded-version (MIME x-www-form-urlencoded) of a string.
2688:             * This version uses the java.net.URLEncoder class to encode it.
2689:             * @param body text to convert
2690:             * @return the URLEncoded text
2691:             */
2692:            protected static String getURLEncode(String body) {
2693:                String rtn = "";
2694:                if (body != null) {
2695:                    rtn = URLEncoder.encode(body); // UTF8?
2696:                }
2697:                return rtn;
2698:            }
2699:
2700:            /**
2701:             * Support routine to return shorter strings.
2702:             * Cuts a string to a amximum length if it exceeds the length specified.
2703:             * @param str the string to shorten
2704:             * @param len the maximum length
2705:             * @return the (possibly shortened) string
2706:             */
2707:            public String getShort(String str, int len) {
2708:                if (str.length() > len) {
2709:                    return str.substring(0, (len - 3)) + "...";
2710:                } else {
2711:                    return str;
2712:                }
2713:            }
2714:
2715:            /**
2716:             * Stores fields information of this table.
2717:             * Asside from the fields supplied by the caller, a field 'otype' is added (if missing).
2718:             *
2719:             * @param f A List with fields (as CoreField objects) as defined by MMBase. This may not be in sync with the actual database table, about which Storage will report then.
2720:             */
2721:            public void setFields(List<CoreField> f) {
2722:                fields.clear();
2723:
2724:                for (CoreField def : f) {
2725:                    String name = def.getName();
2726:                    def.setParent(this );
2727:                    fields.put(name.toLowerCase(), def);
2728:                }
2729:
2730:                // should be TYPE_NODE ???
2731:                if (fields.get(FIELD_OBJECT_TYPE) == null) {
2732:                    log
2733:                            .warn("Object 'otype' field is not defined. Please update your object.xml, or update '"
2734:                                    + getConfigResource()
2735:                                    + "' to extend object");
2736:                    // if not defined in XML (legacy?)
2737:                    // It does currently not work if otype is actually defined in object.xml (as a NODE field)
2738:                    CoreField def = Fields.createSystemField(FIELD_OBJECT_TYPE,
2739:                            Field.TYPE_NODE);
2740:                    def.setGUIName("Type");
2741:                    // here, we should set the DBPos to 2 and adapt those of the others fields
2742:                    def.setStoragePosition(2);
2743:                    def.getDataType().setRequired(true);
2744:                    def.setNotNull(true);
2745:                    for (CoreField field : f) {
2746:                        int pos = field.getStoragePosition();
2747:                        if (pos > 1)
2748:                            field.setStoragePosition(pos + 1);
2749:                    }
2750:                    def.setParent(this );
2751:                    def.finish();
2752:                    fields.put(FIELD_OBJECT_TYPE, def);
2753:                }
2754:                updateFields();
2755:            }
2756:
2757:            /**
2758:             * Sets the subpath of the builder's xml configuration file.
2759:             */
2760:            public void setXMLPath(String m) {
2761:                xmlPath = m;
2762:                update();
2763:            }
2764:
2765:            /**
2766:             * Retrieves the subpath of the builder's xml configuration file.
2767:             * Needed for builders that reside in subdirectories in the builder configuration file directory.
2768:             */
2769:            public String getXMLPath() {
2770:                return xmlPath;
2771:            }
2772:
2773:            /**
2774:             * @since MMBase-1.8
2775:             */
2776:
2777:            public String getConfigResource() {
2778:                return "builders/" + getXMLPath() + "/" + getTableName()
2779:                        + ".xml";
2780:            }
2781:
2782:            /**
2783:             * Gets the file that contains the configuration of this builder
2784:             * @return the builders configuration File object
2785:             * @deprecated Need something as getConfigResource in stead.
2786:             */
2787:            public File getConfigFile() {
2788:                // what is the location of our builder?
2789:                List<File> files = ResourceLoader.getConfigurationRoot()
2790:                        .getFiles(getConfigResource());
2791:                if (files.size() == 0) {
2792:                    return null;
2793:                } else {
2794:                    return files.get(0);
2795:                }
2796:            }
2797:
2798:            /**
2799:             * Set all builder properties
2800:             * Changed properties will not be saved.
2801:             * @param properties the properties to set
2802:             */
2803:            void setInitParameters(Hashtable<String, String> properties) {
2804:                this .properties.putAll(properties);
2805:                loadInitParameters();
2806:                update();
2807:            }
2808:
2809:            /**
2810:             * Get all builder properties
2811:             * @return a <code>Map</code> containing the current properties
2812:             */
2813:            public Map<String, String> getInitParameters() {
2814:                return properties;
2815:            }
2816:
2817:            /**
2818:             * Override properties through application context
2819:             * @param contextPath path in application context where properties are located
2820:             * @since MMBase 1.8.5
2821:             */
2822:            public void loadInitParameters() {
2823:                try {
2824:                    Map<String, String> contextMap = ApplicationContextReader
2825:                            .getProperties("mmbase-builders/" + getTableName());
2826:                    properties.putAll(contextMap);
2827:                } catch (javax.naming.NamingException ne) {
2828:                    log
2829:                            .debug("Can't obtain properties from application context: "
2830:                                    + ne.getMessage());
2831:                }
2832:            }
2833:
2834:            /**
2835:             * Get all builder properties and override properties through application context
2836:             * @param contextPath path in application context where properties are located
2837:             * @return a <code>Map</code> containing the current properties
2838:             * @since MMBase 1.8.2
2839:             * @deprecated
2840:             */
2841:            public Map getInitParameters(String contextPath) {
2842:                Map map = new HashMap();
2843:                map.putAll(getInitParameters());
2844:
2845:                try {
2846:                    Map contextMap = ApplicationContextReader
2847:                            .getProperties(contextPath);
2848:                    if (!contextMap.isEmpty()) {
2849:                        map.putAll(contextMap);
2850:                    }
2851:                } catch (javax.naming.NamingException ne) {
2852:                    log
2853:                            .debug("Can't obtain properties from application context: "
2854:                                    + ne.getMessage());
2855:                }
2856:                return map;
2857:            }
2858:
2859:            /**
2860:             * Set a single builder property
2861:             * The propertie will not be saved.
2862:             * @param name name of the property
2863:             * @param value value of the property
2864:             */
2865:            public void setInitParameter(String name, String value) {
2866:                properties.put(name, value);
2867:                update();
2868:            }
2869:
2870:            /**
2871:             * Retrieve a specific property.
2872:             * @param name the name of the property to get
2873:             * @return the value of the property as a <code>String</code>
2874:             */
2875:            public String getInitParameter(String name) {
2876:                if (properties == null) {
2877:                    return null;
2878:                } else {
2879:                    return properties.get(name);
2880:                }
2881:            }
2882:
2883:            /**
2884:             * Sets the version of this builder
2885:             * @param i the version number
2886:             */
2887:            public void setVersion(int i) {
2888:                version = i;
2889:                update();
2890:            }
2891:
2892:            /**
2893:             * Retrieves the version of this builder
2894:             * @return the version number
2895:             */
2896:            public int getVersion() {
2897:                return version;
2898:            }
2899:
2900:            /**
2901:             * Retrieves the maintainer of this builder
2902:             * @return the name of the maintainer
2903:             */
2904:            public String getMaintainer() {
2905:                return maintainer;
2906:            }
2907:
2908:            /**
2909:             * Sets the maintainer of this builder
2910:             * @param m the name of the maintainer
2911:             */
2912:            public void setMaintainer(String m) {
2913:                maintainer = m;
2914:                update();
2915:            }
2916:
2917:            /**
2918:             * hostname, parses the hostname from a url, so http://www.mmbase.org/bug
2919:             * becomed www.mmbase.org
2920:             * @deprecated Has nothing to do with mmbase nodes. Should be in org.mmbase.util
2921:             */
2922:            public static String hostname_function(String url) {
2923:                if (url.startsWith("http://")) {
2924:                    url = url.substring(7);
2925:                }
2926:                if (url.startsWith("https://")) {
2927:                    url = url.substring(8);
2928:                }
2929:                int pos = url.indexOf("/");
2930:                if (pos != -1) {
2931:                    url = url.substring(0, pos);
2932:                }
2933:                return url;
2934:            }
2935:
2936:            /**
2937:             * Wraps a string.
2938:             * Inserts newlines (\n) into a string at periodic intervals, to simulate wrapping.
2939:             * This also removes whitespace to the start of a line.
2940:             * @param text the text to wrap
2941:             * @param width the maximum width to wrap at
2942:             * @return the wrapped tekst
2943:             */
2944:            public static String wrap(String text, int width) {
2945:                StringBuilder dst = new StringBuilder();
2946:                StringTokenizer tok = new StringTokenizer(text, " \n\r", true);
2947:                int pos = 0;
2948:                while (tok.hasMoreTokens()) {
2949:                    String word = tok.nextToken();
2950:                    if (word.equals("\n")) {
2951:                        pos = 0;
2952:                    } else if (word.equals(" ")) {
2953:                        if (pos == 0) {
2954:                            word = "";
2955:                        } else {
2956:                            pos++;
2957:                            if (pos >= width) {
2958:                                word = "\n";
2959:                                pos = 0;
2960:                            }
2961:                        }
2962:                    } else {
2963:                        pos += word.length();
2964:                        if (pos >= width) {
2965:                            dst.append("\n");
2966:                            pos = word.length();
2967:                        }
2968:                    }
2969:                    dst.append(word);
2970:                }
2971:                return dst.toString();
2972:            }
2973:
2974:            /**
2975:             * Gets a substring.
2976:             * @param value the string to get a substring of
2977:             * @param len the length of the substring
2978:             * @param filler if not null, this field is used as a trailing tekst
2979:             * of the created substring.
2980:             * @return the substring
2981:             */
2982:            private static String substring(String value, int len, String filler) {
2983:                if (filler == null) {
2984:                    if (value.length() > len) {
2985:                        return value.substring(0, len);
2986:                    } else {
2987:                        return value;
2988:                    }
2989:                } else {
2990:                    int len2 = filler.length();
2991:                    if ((value.length() + len2) > len) {
2992:                        return value.substring(0, (len - len2)) + filler;
2993:                    } else {
2994:                        return value;
2995:                    }
2996:                }
2997:            }
2998:
2999:            /**
3000:             * Implmenting a sensible toString is usefull for debugging.
3001:             *
3002:             * @since MMBase-1.6.2
3003:             */
3004:
3005:            public String toString() {
3006:                return getSingularName();
3007:            }
3008:
3009:            /**
3010:             * Equals must be implemented because of the list of MMObjectBuilder which is used for ancestors
3011:             *
3012:             * Declared the method final, because the instanceof operator is used. This is the only
3013:             * MMObjectBuilder is frequently extended and subclasses will always break
3014:             * the equals contract.
3015:             * When subclasses require to implement the equals method then we should use
3016:             * getClass() == o.getClass(), but this has its own issues. For more info, search for equality in Java
3017:             *
3018:             * @since MMBase-1.6.2
3019:             */
3020:            public final boolean equals(Object o) {
3021:                if (o == this )
3022:                    return true;
3023:                if (o == null)
3024:                    return false;
3025:                if (o instanceof  MMObjectBuilder) {
3026:                    MMObjectBuilder b = (MMObjectBuilder) o;
3027:                    return tableName.equals(b.tableName);
3028:                }
3029:                return false;
3030:            }
3031:
3032:            /**
3033:             * @see java.lang.Object#hashCode()
3034:             */
3035:            public int hashCode() {
3036:                return tableName == null ? 0 : tableName.hashCode();
3037:            }
3038:
3039:            /**
3040:             * Implements for MMObjectNode
3041:             * @since MMBase-1.6.2
3042:             */
3043:
3044:            public String toString(MMObjectNode n) {
3045:                return n.defaultToString();
3046:            }
3047:
3048:            /**
3049:             * Implements equals for nodes (this is in MMObjectBuilder because you cannot override MMObjectNode)
3050:             *
3051:             * @since MMBase-1.6.2
3052:             */
3053:
3054:            public boolean equals(MMObjectNode o1, MMObjectNode o2) {
3055:                return o1.defaultEquals(o2);
3056:            }
3057:
3058:            /**
3059:             * Implements for MMObjectNode
3060:             * @since MMBase-1.6.2
3061:             */
3062:
3063:            public int hashCode(MMObjectNode o) {
3064:                return 127 * o.getNumber();
3065:            }
3066:
3067:            /**
3068:             * simple way to register a NodeEvent listener and a RelationEventListener
3069:             * at the same time.
3070:             * @see MMBase#addNodeRelatedEventsListener
3071:             * @param listener
3072:             * @since MMBase-1.8
3073:             */
3074:            public void addEventListener(
3075:                    org.mmbase.core.event.EventListener listener) {
3076:                mmb.addNodeRelatedEventsListener(getTableName(), listener);
3077:            }
3078:
3079:            /**
3080:             * @param listener
3081:             * @since MMBase-1.8
3082:             */
3083:            public void removeEventListener(
3084:                    org.mmbase.core.event.EventListener listener) {
3085:                mmb.removeNodeRelatedEventsListener(getTableName(), listener);
3086:            }
3087:
3088:            /**
3089:             * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
3090:             * here we handle all the backward compatibility stuff.
3091:             * this method covers for both node and relation events.
3092:             * @since MMBase-1.8
3093:             */
3094:            public void notify(NodeEvent event) {
3095:                if (log.isDebugEnabled()) {
3096:                    log.debug("" + this  + " received node event " + event);
3097:                }
3098:                int type = event.getType();
3099:                eventBackwardsCompatible(event.getMachine(), event
3100:                        .getNodeNumber(), type);
3101:            }
3102:
3103:            /**
3104:             * @since MMBase-1.8
3105:             */
3106:            public void notify(RelationEvent event) {
3107:                if (log.isDebugEnabled()) {
3108:                    log.debug("" + this  + " received relation event " + event);
3109:                }
3110:                //for backwards compatibilty: create relation changed calls
3111:                if (event.getRelationSourceType().equals(getTableName())) {
3112:                    eventBackwardsCompatible(event.getMachine(), event
3113:                            .getRelationSourceNumber(),
3114:                            NodeEvent.TYPE_RELATION_CHANGE);
3115:                }
3116:                if (event.getRelationDestinationType().equals(getTableName())) {
3117:                    eventBackwardsCompatible(event.getMachine(), event
3118:                            .getRelationDestinationNumber(),
3119:                            NodeEvent.TYPE_RELATION_CHANGE);
3120:                }
3121:
3122:                //update the cache
3123:                Integer changedNode = Integer
3124:                        .valueOf((event.getRelationDestinationType().equals(
3125:                                getTableName()) ? event
3126:                                .getRelationSourceNumber() : event
3127:                                .getRelationDestinationNumber()));
3128:                MMObjectNode.delRelationsCache(changedNode);
3129:            }
3130:
3131:            /**
3132:             * @see org.mmbase.core.event.NodeEventListener#notify(org.mmbase.core.event.NodeEvent)
3133:             * here we handle all the backward compatibility stuff.
3134:             * this method covers for both node and relation events.
3135:             * @since MMBase-1.8
3136:             * @param event
3137:             */
3138:            private void eventBackwardsCompatible(String machineName,
3139:                    int nodeNumber, int eventType) {
3140:                String ctype = NodeEvent.newTypeToOldType(eventType);
3141:                boolean localEvent = mmb.getMachineName().equals(machineName);
3142:
3143:                if (localEvent) {
3144:                    nodeLocalChanged(machineName, "" + nodeNumber,
3145:                            getTableName(), ctype);
3146:                } else {
3147:                    nodeRemoteChanged(machineName, "" + nodeNumber,
3148:                            getTableName(), ctype);
3149:                }
3150:            }
3151:
3152:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.