/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.cms.ui;

import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.SimpleContainer;
import com.arsdigita.domain.DomainObject;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.domain.DomainServiceInterfaceExposer;
import com.arsdigita.kernel.ui.DomainObjectSelectionModel;
import com.arsdigita.persistence.DataAssociation;
import com.arsdigita.persistence.DataAssociationCursor;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.metadata.ObjectType;
import com.arsdigita.persistence.metadata.Property;
import com.arsdigita.util.Assert;
import com.arsdigita.xml.Element;
import org.apache.log4j.Logger;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @deprecated Use DomainObjectXMLRenderer instead
 * @see com.arsdigita.domain.DomainObjectXMLRenderer
 *
 * A Bebop component that takes a {@link DomainObject} and renders it
 * as XML. The XML can then be styled with XSL in order to insert the
 * object's properties into the page.
 * <p>
 * The XML generated by this component will follow the following pattern:
 * <blockquote><pre><code>
 * &lt;cms:domainObjectRenderer oid="main_object_oid"&gt;
 *   &lt;attributeOne&gt;foo&lt;/attributeOne&gt;
 *   &lt;attributeTwo&gt;bar&lt;/attributeTwo&gt;
 *   &lt;roleReferenceOne oid="child_object_oid"&gt;
 *     &lt;otherAttribute&gt;baz&lt;otherAttribute&gt;
 *     ...
 *   &lt;/roleReferenceOne&gt;
 *   ...
 * &lt;/cms:domainObjectRenderer&gt;
 * </code></pre></blockquote>
 *
 * <p>
 * The object which the <code>DomainObjectRenderer</code> should render is
 * supplied by a {@link DomainObjectSelectionModel}. Thus, an
 * {@link com.arsdigita.kernel.ui.ACSObjectSelectionModel}, an {@link com.arsdigita.cms.ItemSelectionModel}, or any other
 * subclass of {@link DomainObjectSelectionModel} can be used. For example,
 *
 * <blockquote><code><pre>
 * String type = "com.arsdigita.kenel.User";
 * ACSObjectSelectionModel model = new ACSObjectSelectionModel (
 *   type, type, "item_id");
 * page.addGlobalStateParam(model.getStateParameter());
 * page.add(new DomainObjectRenderer(model));
 *
 * <p>
 * Advanced notes:
 * <p>
 * The {@link #setDepth} method controls how detailed the XML will be.
 * At depth 1, only the attributes of the main object will be rendered.
 * At depth 2, the attributes of the main object as well as the children
 * of the main object will be rendered. At depth 3, the children as well as
 * the grandchildren will be rendered... and so on. The default depth is 2.
 * <p>
 * The XML-generating code is aware of loops. Any sub-object which has already
 * been rendered in XML will be rendered as a stub. For example:
 * <blockquote><pre><code>
 * ...
 * &lt;textAsset oid="[com.arsdigita.cms.TextAsset:42]"
 *   &lt;content&gt;I am the text&lt;/content&gt;
 *   &lt;parent oid="[com.arsdigita.cms.TextPage:13]" /&gt;
 *   ...
 * &lt;/textAsset&gt;
 * ...
 * </code></pre></blockquote>
 *
 * Null values are not rendered at all in the XML.
 *
 * @author <a href="mailto:sfreidin@arsdigita.com">Stanislav Freidin</a>
 * @version $Id: //cms/dev/src/com/arsdigita/cms/ui/DomainObjectRenderer.java#11 $
 *
 */

public class DomainObjectRenderer extends SimpleContainer {

    public static final String versionId = "$Id: //cms/dev/src/com/arsdigita/cms/ui/DomainObjectRenderer.java#11 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private DomainObjectSelectionModel m_model;
    private int m_depth;


    public final static String CMS_XML_NS = "http://www.arsdigita.com/cms/1.0";
    private static final Logger s_log = Logger.getLogger(DomainObjectRenderer.class);
    /**
     * Construct a new <code>DomainObjectRenderer</code>.
     *
     * @param model Supplies the domain object to be rendered
     */
    public DomainObjectRenderer(DomainObjectSelectionModel model) {
        this(model, 2);
    }

    /**
     * Construct a new <code>DomainObjectRenderer</code>.
     *
     * @param model Supplies the domain object to be rendered
     * @param depth The object traversal will be limited to this depth
     */
    public DomainObjectRenderer(DomainObjectSelectionModel model, int depth) {
        super();
        m_model = model;
        m_depth = depth;
    }

    /**
     * Return the current depth. The depth is a measure of how detailed
     * the generated XML will be.
     */
    public int getDepth() {
        return m_depth;
    }

    /**
     * Set a new depth. The depth is a measure of how detailed
     * the generated XML will be.
     */
    public void setDepth(int depth) {
        Assert.assertNotLocked(this);
        m_depth = depth;
    }

    /**
     * Return the current selection model, which will supply the domain object
     */
    public DomainObjectSelectionModel getSelectionModel() {
        return m_model;
    }

    /**
     * Set the selection model which will supply the domain object
     * @pre model != null
     */
    public void setSelectionModel(DomainObjectSelectionModel model) {
        Assert.assertNotLocked(this);
        m_model = model;
    }

    /**
     * Return the current domain object
     *
     * @param state the request-specific page state
     */
    public DomainObject getDomainObject(PageState state) {
        return m_model.getSelectedObject(state);
    }

    /**
     * Select a new domain object
     *
     * @param state the request-specific page state
     * @param obj the {@link DomainObject} which will be selected into the
     *   selection model
     */
    public void setDomainObject(PageState state, DomainObject obj) {
        m_model.setSelectedObject(state, obj);
    }

    /**
     * Render the specified object.
     *
     * @param obj the DataObject to be rendered
     * @param elementName the name for the current element
     * @param elementNameSpace the namespace for the current element
     * @param depthRemaining if 0, render the object stub and return. Otherwise,
     *   recurse
     * @param visited a set of all previously visited objects. If the object is
     *   already in the set, render the object stub and return.
     * @return the generated XML element
     */
    protected Element generateXML(
                                  DomainObject obj, String elementName, String elementNameSpace,
                                  int depthRemaining, Set visited
                                  ) {
        Element node = new Element(elementName, elementNameSpace);
        OID oid = obj.getOID();
        node.addAttribute("oid", oid.toString());

        // Check for termination
        if(depthRemaining < 1) return node;

        // Check for duplicates
        if(visited.contains(oid)) return node;

        // Render
        visited.add(oid);

        ObjectType type = oid.getObjectType();

        for(Iterator i = type.getProperties(); i.hasNext(); ) {
            Property prop = (Property) i.next();
            String propName = prop.getName();
            Object propValue = null;

            // This is a hack to get around other hacks in CMS.
            // The hacks deal with retrieving DataCollections of objects;
            // for example,
            // association  {
            //   ContentType[0..n]     allContentTypes;
            //   ContentType[0..n]     allContentTypes2;
            //
            //   retrieve allContentTypes {
            //     ...
            //   }
            // }
            // Trying to traverse this association will cause an error:
            // com.arsdigita.persistence.PersistenceException: Unable to retrieve
            // the associatedContentSectionsForType property of object type
            // ContentType because there is no event handler defined for the
            // retrieve associatedContentSectionsForType event.

            try {
                propValue = DomainServiceInterfaceExposer.
		    get(obj,propName);
            } catch (Exception e) {
                s_log.error("Ignoring some random exception", e);
                // ignore
            }

            if(propValue != null) {

                // Render role references
                if(prop.isRole()) {
                    int newDepth = depthRemaining - 1;

                    if(propValue instanceof DataObject) {
                        // 0..1 or 1..1
                        node.addContent
			    (generateXML
			     (DomainObjectFactory.newInstance
			      ((DataObject)propValue),
			      propName,
			      "",
			      newDepth,
			      visited));
                    } else if(propValue instanceof DataAssociation) {
                        // 0..N
                        DataAssociationCursor daCursor =
                            ((DataAssociation)propValue).getDataAssociationCursor();

                        while (daCursor.next()) {
                            node.addContent
				(generateXML
				 (DomainObjectFactory.newInstance
				  (daCursor.getDataObject()),
				  propName,
				  "",
				  newDepth,
				  visited));
                        }
                    } else {
                        // Unknown property value type - do nothing
                    }

                } else {
                    // Render scalar attributes
                    Element child = new Element(propName);
                    child.setText(propValue.toString());
                    node.addContent(child);
                }
            }
        }

        return node;
    }

    /**
     * Generate XML for the domain object supplied by the
     * selection model.
     *
     * @return the generated element
     */
    public Element generateXMLElement(PageState state) {
        DomainObject obj = getDomainObject(state);
        if(obj != null) {
            return generateXML(
                               obj, "cms:domainObjectRenderer", CMS_XML_NS,
                               getDepth(), new HashSet());
        } else {
            return null;
        }
    }


    /**
     *  Generate XML when you don't have a PageState, only the item
     *
     * @param obj the item to render
     *
     **/
    public Element generateXMLElement(DomainObject obj) {
        return generateXML(
                           obj, "cms:domainObjectRenderer", CMS_XML_NS,
                           getDepth(), new HashSet());
    }


    /**
     * Generate XML for the domain object supplied by the
     * selection model.
     */
    public void generateXML(PageState state, Element parent) {
        Element e = generateXMLElement(state);
        if(e != null)
            parent.addContent(e);
    }


}
