This hack is voor MMBase 1.8
It's intention is to merge all code presently in the ObjectTypes builder in the TypeDef builder.
This move makes the ObjectTypes obsolete so it can be deleted, which removes some ambiguity in the use of these two builders.


Included is the new source of the TypeDef builder.
Note also that the typedef.xml configuration file need be changed to point to the TypeDef class (once again) instead of to ObjectTypes.


This hack is part of step 8 of the Optimization project.

START:             2004/10/25
START OF VOTING:   2004/10/26 13:00
END OF CALL:       2004/10/29 13:00

 [_] +1 (YES)
 [_] +0 (ABSTAIN )
 [_] -1 (NO), because :
 [_] VETO, because:


-- Pierre van Rooden Mediapark, C 107 tel. +31 (0)35 6772815 "Never summon anything bigger than your head."
/*

This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.

The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license

*/
package org.mmbase.module.corebuilders;

import java.io.*;
import java.util.*;

import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;
import org.xml.sax.InputSource;

import org.mmbase.module.core.*;
import org.mmbase.util.PageInfo;
import org.mmbase.util.StringTagger;
import org.mmbase.util.logging.*;
import org.mmbase.util.xml.BuilderReader;


/**
 * TypeDef is used to define the* object types (builders).
 * Nodes of this builder have a vitual 'config' field.
 * This field contains the xml-Document of the builder represented by the node.
 * The filename used to refernce the xml document uis derived by extending the field 
'name'.
 * Creating a new typedef node automatically creates a new xml file and loads a new 
builder.
 * Removing a node dropsa nd unloads a builder (including the xml).
 * Changes to the config will also be active on commit of the node.
 *
 * @author Daniel Ockeloen
 * @author Pierre van Rooden
 * @version $Id: TypeDef.java,v 1.41 2004/10/25 12:02:43 pierre Exp $
 */
public class TypeDef extends MMObjectBuilder {

    /**
     * The property in the builder file ('deploy-dir') that sets the directory
     * where new builder configuration files are to be deployed.
     */
    public static final String PROPERTY_DEPLOY_DIR = "deploy-dir";

    // Logger routine
    private static final Logger log = Logging.getLoggerInstance(TypeDef.class);
    // Directory where new builder configuration files are deployed by default
    private File defaultDeploy = null;
    // if true, auto-deploying builders (and saving configfiles) is possible
    private boolean autoDeploy = true;

    /**
     * Number-to-name cache.
     * @duplicate should be moved to org.mmbase.cache
     */
    private Map numberToNameCache = null; // object number -> typedef name

    /**
     * Name-to-number cache.
     * @duplicate should be moved to org.mmbase.cache
     */
    private Map nameToNumberCache = null; // typedef name -> object number

    /**
     * List of known builders.
     */
    private Vector typedefsLoaded = new Vector();     // Contains the names of all 
active builders

    /**
     * Constructor
     */
    public TypeDef() {
        // set broadcasting of changes to false
        broadcastChanges = false;
    }

    /**
     * Sets the default deploy directory for the builders.
     * @return true if init was completed, false if uncompleted.
     */
    public boolean init() {
        boolean result = super.init();
        if (defaultDeploy == null) {
            // determine default deploy directory
            String builderDeployDir = getInitParameter(PROPERTY_DEPLOY_DIR);
            if (builderDeployDir == null) {
                builderDeployDir = "applications";
            }
            defaultDeploy = new File(MMBaseContext.getConfigPath() + File.separator + 
"builders" + File.separator + builderDeployDir);
            // create the dir, when it wasnt there...
            if (!defaultDeploy.exists()) {
                // try to create the directory for deployment....
                if (!defaultDeploy.mkdirs() || !defaultDeploy.canWrite()) {
                    log.error("Could not create or write in directory: " + 
defaultDeploy + ", auto-builder deploy turned off");
                    autoDeploy = false;
                }
            }
            defaultDeploy = defaultDeploy.getAbsoluteFile();
            log.service("Using '" + defaultDeploy + "' as default deploy dir for our 
builders.");
        }
        return result;
    }

    protected Map getNumberToNameCache() {
        if (numberToNameCache == null) readCache();
        return numberToNameCache;
    }

    protected Map getNameToNumberCache() {
        if (nameToNumberCache == null) readCache();
        return nameToNumberCache;
    }

    /**
     * Insert a new object (content provided) in the cloud, including an entry for the 
object alias (if provided).
     * This method indirectly calls [EMAIL PROTECTED] #preCommit}.
     * Asside from that, this method loads the builder this node represents, and 
initalizes it. If you do
     * not wish to load the builder (i.e. because it is already loaded), use [EMAIL 
PROTECTED] #insert(String, MMObjectNode, boolean)}
     * @param owner The administrator creating the node
     * @param node The object to insert. The object need be of the same type as the 
current builder.
     * @return An <code>int</code> value which is the new object's unique number, -1 
if the insert failed.
     */
    public int insert(String owner, MMObjectNode node) {
        return insert(owner, node, true);
    }

    /**
     * Insert a new object (content provided) in the cloud, including an entry for the 
object alias (if provided).
     * This method indirectly calls [EMAIL PROTECTED] #preCommit}.
     * @param owner The administrator creating the node
     * @param node The object to insert. The object need be of the same type as the 
current builder.
     * @param loadBuilder if <code>true</code>, the builder should be loaded. This 
method is set to
     *        <code>false</code> when it is called from the init() method of 
MMObjectBuilder to prevent
     *        it from being loaded twice
     * @return An <code>int</code> value which is the new object's unique number, -1 
if the insert failed.
     */
    public int insert(String owner, MMObjectNode node, boolean loadBuilder) {
        log.service("Insert of builder-node with name '" + node.getStringValue("name") 
+ "'");
        // store config if changed or new
        storeBuilderConfiguration(node);
        // try if the builder was already in TypeDef for some reason
        // this can happen when another thread was here first
        int result = getIntValue(node.getStringValue("name"));
        if (result < 0) {
            // otherwise save the node
            result = super.insert(owner, node);
        }
        if (result != -1) {
            // update the cache
            Integer number = new Integer(result);
            String name = node.getStringValue("name");
            getNameToNumberCache().put(name, number);
            getNumberToNameCache().put(number, name);
            // Load the builder if needed
            if (loadBuilder) {
                loadBuilder(node);
            }
        }
        return result;
    }

    /**
     * Commit changes to this node to the database. This method indirectly calls 
[EMAIL PROTECTED] #preCommit}.
     * Use only to commit changes - for adding node, use [EMAIL PROTECTED] #insert}.
     * @param node The node to be committed
     * @return true if commit successful
     */
    public boolean commit(MMObjectNode node) {
        log.service("Commit of builder-node with name '" + node.getStringValue("name") 
+ "' ( #" + node.getNumber() + ")");
        MMObjectBuilder builder = getBuilder(node);
        BuilderReader originalBuilderXml = new 
BuilderReader(getBuilderConfigurationPath(node), getMMBase());
        BuilderReader newBuilderXml      = new BuilderReader(new InputSource(new 
StringReader(
                                                 "<?xml version=\"1.0\" 
encoding=\"UTF-8\"?>\n" +
                                                 "<!DOCTYPE builder PUBLIC \"" + 
BuilderReader.PUBLIC_ID_BUILDER +
                                                 "\" \":http://www.mmbase.org/dtd/"; + 
BuilderReader.DTD_BUILDER + "\" >\n" +
                                                 node.getStringValue("config"))), 
getMMBase());
        if (!originalBuilderXml.equals(newBuilderXml)) {
            try {
                // unload the builder...
                builder = unloadBuilder(node);
                // attempt to apply changes to the database
                // by dropping the buildertable (ARGH!)
                if (! originalBuilderXml.storageEquals(newBuilderXml)) {
                    builder.delete();
                }
                // finally save our new config.
                storeBuilderConfiguration(node);
            } finally {
                // clear config, so it will be refreshed later on
                node.storeValue("config",null);
                // load the builder again.. (will possibly create a new table)
                loadBuilder(node);
            }
        }
        return super.commit(node);
    }

    /**
     * Remove a node from the cloud, when the represented builder was active
     * it will also be unloaded
     * @param node The node to remove.
     * @throws RuntimeException When the operation could not be performed
     */
    public void removeNode(MMObjectNode node) {
        log.info("Remove of builder-node with name '" + node.getStringValue("name") + 
"' ( #" + node.getNumber() + ")");
        // only delete when builder is completely empty...
        MMObjectBuilder builder = getBuilder(node);
        testBuilderInUse(builder);
        builder = unloadBuilder(node);
        // now that the builder cannot be started again (since config is now really 
missing)
        builder.delete();
        // try to delete the configuration file first!.....
        if (!deleteBuilderConfiguration(node)) {
            // delete-ing failed, reload the builder again...
            loadBuilder(node);
            throw new RuntimeException("Could not delete builder config");
        }
        Integer number=node.getIntegerValue("number");
        String name=node.getStringValue("name");
        super.removeNode(node);
        getNameToNumberCache().remove(name);
        getNumberToNameCache().remove(number);
    }

    /**
     * Fill the typedef caches with the initial values.
     * Caches filled are a number-to-name and a name-to-number cache.
     * @duplicate should be moved to org.mmbase.cache
     * @return always true
     */
    private boolean readCache() {
        // at least fill in typedef
        log.service("Reading typedef caches");
        numberToNameCache = new Hashtable();
        nameToNumberCache = new Hashtable();
        for (Enumeration e = search(null); e.hasMoreElements();) {
            MMObjectNode n= (MMObjectNode) e.nextElement();
            Integer number= n.getIntegerValue("number");
            String name  = n.getStringValue("name");
            if (number != null && name != null) {
                nameToNumberCache.put(name,number);
                numberToNameCache.put(number,name);
            } else {
                log.error("Could not add typedef cache-entry number/name= " + number + 
"/" + name);
            }
         }
        return true;
    }

    /**
     * Obtain the type value of the requested builder
     * @todo smarter cache update
     * @param builderName name of the builder
     * @return the object type as an int, -1 if not defined.
     */
    public int getIntValue(String builderName) {
        Integer result=(Integer)getNameToNumberCache().get(builderName);
        if (result!=null) {
            return result.intValue();
        } else {
            return -1;
        }
    }

    /**
     * Obtain the buildername of the requested type
     * @param type the object type
     * @return the name of the builder as a string, null if not found
     */
    public String getValue(int type) {
        String result = (String) getNumberToNameCache().get(new Integer(type));
        if (result == null) {
            log.warn("Could not find builder name for typedef number " + type);
        }
        return result;
    }

    /**
     * Obtain the buildername of the requested type
     * @param type the object type
     * @return the name of the builder as a string, "unknown" if not found
     * @deprecated use getValue(int)
     */
    public String getValue(String type) {
        try {
            return (String) getNumberToNameCache().get(new 
Integer(Integer.parseInt(type)));
        } catch(Exception e) {
            return "unknown";
        }
    }

    /**
     * @javadoc
     */
    public String getSingularName(String builderName, String language) {
        if (builderName == null) return "unknown";
        MMObjectBuilder bul=(MMObjectBuilder)mmb.mmobjs.get(builderName);
        if (bul!=null) {
            if (language == null) {
                return bul.getSingularName();
            } else {
                return bul.getSingularName(language);
            }
        } else {
            return "inactive ("+builderName+")";
        }
    }

    /**
     * @javadoc
     */
    public boolean isRelationTable(String name) {
        return mmb.getRelDef().isRelationTable(name);
    }

    /**
     * Provides additional functionality when obtaining field values.
     * This method is called whenever a Node of the builder's type fails at evaluating 
a getValue() request
     * (generally when a fieldname is supplied that doesn't exist).
     * It allows the system to add 'functions' to be included with a field name, such 
as 'html(body)' or 'time(lastmodified)'.
     * This method will parse the fieldname, determining functions and calling the 
[EMAIL PROTECTED] #executeFunction} method to handle it.
     * Functions in fieldnames can be given in the format 'functionname(fieldname)'. 
An old format allows 'functionname_fieldname' instead,
     * though this only applies to the text functions 'short', 'html', and 'wap'.
     * Functions can be nested, i.e. 'html(shorted(body))'.
     * Derived builders should override this method only if they want to provide 
virtual fieldnames. To provide addiitonal functions,
     * override [EMAIL PROTECTED] #executeFunction} instead.
     * @param node the node whos efields are queries
     * @param field the fieldname that is requested
     * @return the result of the 'function', or null if no valid functions could be 
determined.
     */
    public Object getValue(MMObjectNode node, String field) {
        if (log.isDebugEnabled()) {
            log.debug("node:" + node + " field: " + field);
        }
        // return the Document from the config file..
        if (field.equals("config")) {
            // first check if we already have a value in node fields...
            Object o = super.getValue(node, field);
            if (o != null) {
                return o;
            }
            // otherwise, open the file to return it...
            if (log.isDebugEnabled()) {
               log.debug("retrieving the document for node #" + node.getNumber());
            }
            try {
                // method node.getStringValue("name") should work, since 
getStringValue("path") checked it already...
                File file = new File(getBuilderConfigurationPath(node));
                // when the file doesnt exist, the value we return should be null...
                if (!file.exists()) {
                    log.warn("file with name: " + file + " didnt exist, getValue will 
return null for builder config");
                    return null;
                }
                org.w3c.dom.Document doc = 
org.mmbase.util.XMLBasicReader.getDocumentBuilder(org.mmbase.util.xml.BuilderReader.class).parse(file);
                // set the value in the node fields..
                node.setValue(field, doc);
                return doc;
            } catch (org.xml.sax.SAXException se) {
                throw new RuntimeException(Logging.stackTrace(se));
            } catch (java.io.IOException ioe) {
                throw new RuntimeException(Logging.stackTrace(ioe));
            }
        } else if (field.equals("state")) {
            int val=node.getIntValue("state");
            // is it set allready ? if not set it, this code should be
            // removed ones the autoreloader/state code is done.
            if (val==-1) {
                // state 1 is up and running
                node.setValue("state",1);
            }
            return ""+val;
        } else if (field.equals("dutchs(name)")) {
            // replace this function with gui(name) ?
            // but have to change admin pages first
            return getGUIIndicator("name",node);
        }
        return super.getValue(node, field);
    }

    /**
     *  Sets a key/value pair in the main values of this node.
     *  Note that if this node is a node in cache, the changes are immediately visible 
to
     *  everyone, even if the changes are not committed.
     *  The fieldname is added to the (public) 'changed' vector to track changes.
     *  @param fieldname the name of the field to change
     *  @param fieldValue the value to assign
     *  @param originalValue the value which was original in the field
     *  @return <code>true</code> When an update is required(when changed),
     *  <code>false</code> if original value was set back into the field.
     */
    public boolean setValue(MMObjectNode node, String fieldName, Object originalValue) 
{
        Object newValue = node.retrieveValue(fieldName);
        // the field with the name 'name' may not be changed.....
        if (originalValue != null && !originalValue.equals(newValue)) {
            if (fieldName.equals("name")) {
                // restore the original value...
                node.storeValue(fieldName, originalValue);
                throw new RuntimeException("Cannot change a builder's name");
/*            } else if (fieldName.equals("config")) {
                MMObjectBuilder builder = getBuilder(node);
                // TODO: active / not active code.. IT CAN MESS UP BUILDERS THAT ARE 
SET INACTIVE, AND STILL HAVE DATA IN DATABASE!
                if (builder == null) {
                    log.warn("No builder found for typedef node " + node);
                } else if (builder.size() > 0) {
                    throw new RuntimeException("Cannot change builder config it has 
nodes (otherwise information could get lost..)");
                } else {
                    log.info("Changing config for typedef " + node + " associated with 
builder '" + builder.getTableName() + "'");
                }
*/
            }
        }
        return true;
    }

    /**
     * @javadoc
     */
    public boolean fieldLocalChanged(String number,String builder,String field,String 
value) {
        if (field.equals("state")) {
            if (value.equals("4")) {
                // reload request
                log.service("Reload wanted on : "+builder);
                // perform reload
                MMObjectNode node=getNode(number);
                String objectname=node.getStringValue("name");
                reloadBuilder(objectname);
                if (node!=null) {
                    node.setValue("state",1);
                }
            }
        }
        return true;
    }

    /**
     * Returns the MMObjectBuilder which is represented by the node.
     * @param   node The node, from which we want to know its MMObjectBuilder
     * @return  The builder which is represented by the node, or <code>null</code>
     *          if the builder was not loaded.
     */
    public MMObjectBuilder getBuilder(MMObjectNode node) {
        String builderName = node.getStringValue("name");
        return mmb.getMMObject(builderName);
    }

    /**
     * @javadoc
     */
    public boolean reloadBuilder(String objectname) {
        log.service("Trying to reload builder : "+objectname);
        // first get all the info we need from the builder allready running
        MMObjectBuilder oldbul=mmb.getMMObject(objectname);
        String classname=oldbul.getClass().getName();
        String description=oldbul.getDescription();

        try {
            Class newclass=Class.forName("org.mmbase.module.builders."+classname);
            log.debug("Loaded load class : "+newclass);

            MMObjectBuilder bul = (MMObjectBuilder)newclass.newInstance();
            log.debug("Started : "+newclass);

            bul.setMMBase(mmb);
            bul.setTableName(objectname);
            bul.setDescription(description);
            bul.init();
            mmb.mmobjs.put(objectname,bul);
        } catch (Exception e) {
            log.error(Logging.stackTrace(e));
            return false;
        }
        return true;
    }

    /**
     * What should a GUI display for this node.
     * This method returns the gui name (singular name) of the builder that goes with 
this node.
     * @param node The node to display
     * @return the display of the node as a <code>String</code>
     */
    public String getGUIIndicator(MMObjectNode node) {
        return getSingularName(node.getStringValue("name"), null);
    }

    /**
     * What should a GUI display for this node/field combo.
     * If the field specified is 'name', this method returns the gui name (singular 
name) of the
     * builder that goes with this node.
     * @param field the name field of the field to display
     * @param node The node to display
     * @return the display of the node's field as a <code>String</code>, null if not 
specified
     */
    public String getGUIIndicator(String field, MMObjectNode node) {
        if (field.equals("name")) {
            String name = node.getStringValue("name");
            String guiname= getSingularName(name, null);
            return guiname;
        }
        return null;
    }

    /**
     * The GUIIndicator can depend on the locale. Override this function
     * @since MMBase-1.6
     */
    protected String getLocaleGUIIndicator(Locale locale, String field, MMObjectNode 
node) {
        if (field == null || "".equals(field) || "name".equals(field)){
            return getLocaleGUIIndicator(locale, node);
        }
        return null;
    }

    protected String getLocaleGUIIndicator(Locale locale, MMObjectNode node) {
        String rtn =  getSingularName(node.getStringValue("name"), 
locale.getLanguage());
        if (rtn == null) return node.getStringValue("name");
        return rtn;
    }

    /**
     * @javadoc
     */
    public void loadTypeDef(String name) {
        if(!typedefsLoaded.contains(name)) {
            typedefsLoaded.add(name);
        } else {
            if (log.isDebugEnabled()) log.debug("Builder "+name+" is already loaded!");
        }
    }

    /**
     * @javadoc
     */
    public void unloadTypeDef(String name) {
        if(typedefsLoaded.contains(name)) {
            typedefsLoaded.remove(name);
        } else {
            log.debug("Builder "+name+" is not loaded!");
        }
    }

    /**
     * @javadoc
     */
    public Vector getList(PageInfo sp,StringTagger tagger, StringTokenizer tok) {
        if (tok.hasMoreTokens()) {
            String cmd=tok.nextToken();
            if (cmd.equals("builders")) {
                return typedefsLoaded;
            }
        }
        return null;
    }

    protected Object executeFunction(MMObjectNode node, String function, List args) {
        log.debug("executefunction of typedef");
        if (function.equals("info")) {
            List empty = new ArrayList();
            java.util.Map info = (java.util.Map) super.executeFunction(node, function, 
empty);
            info.put("gui", info.get("info") + " (localized)");
            if (args == null || args.size() == 0) {
                return info;
            } else {
                return info.get(args.get(0));
            }
        } else if (function.equals("gui")) {
            log.debug("GUI of servlet builder with " + args);
            if (args == null || args.size() ==0) {
                return getGUIIndicator(node);
            } else {
                String rtn;
                if (args.size() <= 1) {
                    rtn = getGUIIndicator((String) args.get(0), node);
                } else {
                    String language = (String) args.get(1);
                    if (language == null) language = mmb.getLanguage();
                    Locale locale = new Locale(language, "");
                    rtn = getLocaleGUIIndicator(locale, (String) args.get(0), node);
                }
                if (rtn == null) return super.executeFunction(node, function, args);
                return rtn;
            }
        } else if (function.equals("defaultsearchage")) {
            return new Integer(getBuilder(node).getSearchAge());
        } else {
            return super.executeFunction(node, function, args);
        }
    }

    private void testBuilderInUse(MMObjectBuilder builder) {
        if (builder == null) {
            throw new RuntimeException("Cannot delete inactive builders (because it is 
unknown what the state of the table in the database is)");
        } else if (builder.size() > 0) {
            throw new RuntimeException("Cannot delete this builder, it still contains 
nodes");
        } else {
            if (builder instanceof InsRel) {
                MMObjectNode reldef = 
mmb.getRelDef().getDefaultForBuilder((InsRel)builder);
                if (reldef != null) {
                    throw new RuntimeException("Cannot delete this builder, it is 
referenced in reldef #" + reldef.getNumber());
                }
            }
            Enumeration e = mmb.getTypeRel().search("WHERE snumber=" + builder.oType + 
" OR dnumber=" + builder.oType);
            if (e.hasMoreElements()) {
                String typerels = "#" + ((MMObjectNode)e.nextElement()).getNumber();
                while (e.hasMoreElements()) {
                    typerels = typerels + ", #" + 
((MMObjectNode)e.nextElement()).getNumber();
                }
                throw new RuntimeException("Cannot delete this builder, it is 
referenced by typerels: " + typerels);
            }
        }
    }

    /**
     * Returns the path, where the builder configuration file can be found, for not 
exising builders, a path will be generated.
     * @param   node The node, from which we want to know it;s MMObjectBuilder
     * @return  The path where the builder should live or <code>null</code> in case of 
strange failures
     *          When the builder was not loaded.
     */
    protected String getBuilderConfigurationPath(MMObjectNode node) {
        // call our code above, to get our path...
        String path = getBuilderPath(node);
        // do we have a path?
        if (path == null) {
            log.error("field 'path' was empty.");
            return null;
        }
        return path + File.separator + node.getStringValue("name") + ".xml";
    }

    /**
     * Returns the path, where the builder configuration file can be found, for not 
exising builders, a path will be generated.
     * @param   node The node, from which we want to know it;s MMObjectBuilder
     * @return  The path where the builder should live or <code>null</code> in case of 
strange failures
     *          When the builder was not loaded.
     */
    protected String getBuilderPath(MMObjectNode node) {
        if (log.isDebugEnabled()) {
            log.debug("retrieving the path for node #" + node.getNumber());
        }
        // some basic checking
        if (node == null) {
            log.error("node was null");
            return null;
        }
        if (node.getStringValue("name") == null) {
            log.error("field 'name' was null");
            return null;
        }
        if (node.getStringValue("name").trim().length() == 0) {
            log.error("field 'name' was empty.");
            return null;
        }
        // first request the url from the active builder....
        MMObjectBuilder builder = getBuilder(node);
        if (builder != null) {
            // return the file path,..
            String file = builder.getConfigFile().getAbsoluteFile().getParent();
            if (log.isDebugEnabled()) {
                log.debug("builder file:" + file);
            }
            return file;
        }
        // builder was inactive... try to get the correct path in some other way :(
        String pathInBuilderDir = mmb.getBuilderPath(node.getStringValue("name"), "");
        if (pathInBuilderDir != null) {
            // return the file path,..
            String file = MMBaseContext.getConfigPath() + File.separator + "builders" 
+ File.separator + pathInBuilderDir;
            if (log.isDebugEnabled()) {
                log.debug("builder file:" + file);
            }
            return file;
        }
        // still null, make up a nice url for our builder!
        String file = defaultDeploy.getPath();
        if (log.isDebugEnabled())
            log.debug("builder file:" + file);
        return file;
    }

    /**
     */
    protected MMObjectBuilder loadBuilder(MMObjectNode node) {
        if (log.isDebugEnabled()) {
            log.debug("Load builder '" + node.getStringValue("name") + "' ( #" + 
node.getNumber() + ")");
        }
        String path = getBuilderPath(node);
        // remove everything till last '/builders/'
        // TODO: find a better way for whole file location stuff
        String search = File.separator + "builders";
        int pos = path.lastIndexOf(search);
        if (pos == -1) {
            String msg = "could not retrieve the path to store the file..(path: " + 
path + " search: " + search + ")";
            log.fatal(msg);
            throw new RuntimeException(msg);
        }
        path = path.substring(pos + search.length()) + File.separator;
        MMObjectBuilder builder = mmb.loadBuilderFromXML(node.getStringValue("name"), 
path);
        if (builder == null) {
            // inactive builder?
            log.info("could not load builder from xml, is in inactive?(name: '" + 
node.getStringValue("name") + "' path: '" + path + "')");
            return null;
        }
        mmb.initBuilder(builder);
        return builder;
    }

    /**
     */
    protected File storeBuilderConfiguration(MMObjectNode node) {
        if (log.isDebugEnabled()) {
            log.debug("Store builder '" + node.getStringValue("name") + "' ( #" + 
node.getNumber() + ")");
        }
        org.w3c.dom.Document doc = node.getXMLValue("config");
        if (doc == null) {
            throw new RuntimeException("Field config was null! Could not save the 
file");
        }
        File file = new File(getBuilderConfigurationPath(node));
        if (file == null) {
            throw new RuntimeException("file was null, could not continue");
        }
        if (file.exists()) {
            if (log.isDebugEnabled()) {
                log.debug("found file: " + file + ", only store when changed.");
            }
            // we already had a file, look if we have to save it (only needed when was 
modified)
            try {
                org.w3c.dom.Document original = 
org.mmbase.util.XMLBasicReader.getDocumentBuilder(org.mmbase.util.xml.BuilderReader.class).parse(file);
                if (compareDocuments(doc, original)) {
                    // doc's were the same..
                    if (log.isDebugEnabled()) {
                        log.debug("document already there, with same data, xml will 
not be written to file:" + file);
                    }
                    return file;
                }
            } catch (org.xml.sax.SAXException se) {
                // original document wasnt a xml document?
                log.warn("found an other file on location, which wasnt xml(can't 
compare), overwriting the file with current config.(error:" + se.toString() + ")");
            } catch (java.io.IOException ioe) {
                // original document gave an io exception, strange...
                throw new RuntimeException("failure opening old configuration for 
comparison, error: " + ioe.toString());
            }
        }
        String message = "";
        try {
            Transformer transformer = 
TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, mmb.getEncoding());
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, 
doc.getDoctype().getPublicId());
            transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, 
doc.getDoctype().getSystemId());
            log.service("Saving builderconfig to file:" + file);
            transformer.transform(new DOMSource(doc), new StreamResult(file));
        } catch (TransformerException te) {
            message = "Failure saving configuration to disk : " + te.getMessage() + 
"\nbuilder-doc:\n" + doc + "\nbuilder-rootelement:\n" + doc.getDocumentElement();
            // throw new RuntimeException("
            // storing the builder failed!
        }
        if (! file.exists()) {
            log.error("Failed to store file " + file + ": " + message);
        } else {
            log.service("Created file :" + file);
        }
        return file;
    }

    /**
     *  documents may not be null!
     */
    private boolean compareDocuments(org.w3c.dom.Document a, org.w3c.dom.Document b) {
        try {
            //make a string from the XML
            TransformerFactory tfactory = TransformerFactory.newInstance();
            Transformer serializer = tfactory.newTransformer();
            // maybe some better code?
            StringWriter asw = new StringWriter();
            serializer.transform(new DOMSource(a), new StreamResult(asw));
            StringWriter bsw = new StringWriter();
            serializer.transform(new DOMSource(b), new StreamResult(bsw));
            // compare the 2 document-strings
            return asw.toString().equals(bsw.toString());
        } catch (TransformerConfigurationException tce) {
            String message = tce.toString() + " " + Logging.stackTrace(tce);
            log.error(message);
            throw new RuntimeException(message);
        } catch (TransformerException te) {
            String message = te.toString() + " " + Logging.stackTrace(te);
            log.error(message);
            throw new RuntimeException(message);
        }
    }

    /**
     */
    protected MMObjectBuilder unloadBuilder(MMObjectNode node) {
        if (log.isDebugEnabled()) {
            log.debug("Unload builder '" + node.getStringValue("name") + "' ( #" + 
node.getNumber() + ")");
        }
        // unload the builder,...
        MMObjectBuilder builder = getBuilder(node);
        if (builder != null) {
            mmb.unloadBuilder(builder);
        }
        return builder;
    }

    /**
     */
    protected boolean deleteBuilderConfiguration(MMObjectNode node) {
        if (log.isDebugEnabled()) {
            log.debug("Delete file of builder '" + node.getStringValue("name") + "' ( 
#" + node.getNumber() + ")");
        }
        File file = new File(getBuilderConfigurationPath(node));
        if (file.exists()) {
            if (!file.canWrite()) {
                log.error("file: " + file + " had no write rights for me.");
                return false;
            }
            // remove the file from the file system..
            file.delete();
            if (log.isDebugEnabled()) {
                log.debug("file: " + file + " has been deleted");
            }
        }
        return true;
    }
}

Reply via email to