/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jetspeed.om.page.psml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.apache.jetspeed.JetspeedActions;
import org.apache.jetspeed.idgenerator.IdGenerator;
import org.apache.jetspeed.om.folder.Folder;
import org.apache.jetspeed.om.page.BaseFragmentElement;
import org.apache.jetspeed.om.page.BaseFragmentValidationListener;
import org.apache.jetspeed.om.page.Fragment;

/**
 * @version $Id: FragmentImpl.java 881862 2009-11-18 18:24:48Z rwatler $
 */
public class FragmentImpl extends AbstractBaseFragmentElement implements Fragment, java.io.Serializable
{
    private String type = null;

    private List fragments = new ArrayList();

    private List fragmentElementImpls = new ArrayList();

    private String name;

    private FragmentList fragmentsList;

    /**
     * <p>
     * Default Constructor.
     * </p>
     */
    public FragmentImpl()
    {
    }

    public String getType()
    {
        return this.type;
    }

    public void setType( String type )
    {
        this.type = type;
    }

    List accessFragments()
    {
        return fragments;
    }

    public List getFragments()
    {
        // create and return mutable fragments collection
        // filtered by view access
        if (fragmentsList == null)
        {
            fragmentsList = new FragmentList(this);
        }
        return filterFragmentsByAccess(fragmentsList);
    }

    /**
     * getFragmentElementImpls - get list of wrapped fragment elements
     *
     * @return wrapped element list
     */
    public List getFragmentElementImpls()
    {
        return fragmentElementImpls;
    }

    /**
     * setFragmentElementImpls - set list of wrapped fragment elements
     *
     * @param elements wrapped element list
     */
    public void setFragmentElementImpls(List elements)
    {
        fragmentElementImpls = elements;
    }
    
    /**
     * <p>
     * getName
     * </p>
     * 
     * @see org.apache.jetspeed.om.page.Fragment#getName()
     * @return
     */
    public String getName()
    {
        return name;
    }

    /**
     * <p>
     * setName
     * </p>
     * 
     * @see org.apache.jetspeed.om.page.Fragment#setName(java.lang.String)
     * @param name
     */
    public void setName( String name )
    {
        this.name = name;

    }

    void setBaseFragmentsElement(AbstractBaseFragmentsElement baseFragmentsElement)
    {
        // set base fragments implementation
        super.setBaseFragmentsElement(baseFragmentsElement);
        // propagate to children
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                ((AbstractBaseFragmentElement)fragmentsIter.next()).setBaseFragmentsElement(baseFragmentsElement);
            }
        }
    }
    
    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.Fragment#getFragmentById(java.lang.String)
     */
    public BaseFragmentElement getFragmentById( String id )
    {
        Stack stack = new Stack();
        Iterator i = getFragments().iterator();
        while (i.hasNext())
        {
            stack.push(i.next());
        }

        BaseFragmentElement f = (BaseFragmentElement) stack.pop();

        while ((f != null) && (!(f.getId().equals(id))))
        {
            if (f instanceof Fragment)
            {
                i = ((Fragment)f).getFragments().iterator();

                while (i.hasNext())
                {
                    stack.push(i.next());
                }
            }

            if (stack.size() > 0)
            {
                f = (BaseFragmentElement) stack.pop();
            }
            else
            {
                f = null;
            }
        }

        return f;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.Fragment#removeFragmentById(java.lang.String)
     */
    public BaseFragmentElement removeFragmentById( String id )
    {
        // find fragment by id, tracking fragment parent
        Map parents = new HashMap();
        Stack stack = new Stack();
        Iterator i = getFragments().iterator();
        while (i.hasNext())
        {
            stack.push(i.next());
        }

        BaseFragmentElement f = (BaseFragmentElement) stack.pop();
        while ((f != null) && (!(f.getId().equals(id))))
        {
            if (f instanceof Fragment)
            {
                i = ((Fragment)f).getFragments().iterator();

                while (i.hasNext())
                {
                    BaseFragmentElement child = (BaseFragmentElement)i.next();
                    stack.push(child);
                    parents.put(child, f);
                }
            }

            if (stack.size() > 0)
            {
                f = (BaseFragmentElement) stack.pop();
            }
            else
            {
                f = null;
            }
        }

        // remove fragment from parent/fragments
        if (f != null)
        {
            BaseFragmentElement parent = (BaseFragmentElement)parents.get(f);
            if (parent != null)
            {
                if (parent instanceof Fragment)
                {
                    if (((Fragment)parent).getFragments().remove(f))
                    {
                        return f;
                    }
                }
            }
            else
            {
                if (getFragments().remove(f))
                {
                    return f;
                }
            }
        }

        // not found or removed
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.psml.AbstractElementImpl#getLogicalPermissionPath()
     */
    public String getLogicalPermissionPath()
    {
        // use base fragments implementation path as base and append name
        if ((getBaseFragmentsElement() != null) && (getName() != null))
        {
            return getBaseFragmentsElement().getLogicalPermissionPath() + Folder.PATH_SEPARATOR + getName();
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.psml.AbstractBaseElementImpl#getPhysicalPermissionPath()
     */
    public String getPhysicalPermissionPath()
    {
        // use base fragments implementation path as base and append name
        if ((getBaseFragmentsElement() != null) && (getName() != null))
        {
            return getBaseFragmentsElement().getPhysicalPermissionPath() + Folder.PATH_SEPARATOR + getName();
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.psml.AbstractBaseFragmentElement#validateFragments(org.apache.jetspeed.om.page.BaseFragmentValidationListener)
     */
    protected boolean validateFragments(BaseFragmentValidationListener validationListener)
    {
        // validate fragment using validation listener
        if (!validationListener.validate(this))
        {
            return false;
        }
        // validate fragments using validation listener
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                if (!((AbstractBaseFragmentElement)fragmentsIter.next()).validateFragments(validationListener))
                {
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * unmarshalled - notification that this instance has been
     *                loaded from the persistent store
     * @param generator id generator
     * @return dirty flag
     */
    public boolean unmarshalled(IdGenerator generator)
    {
        // notify super class implementation
        boolean dirty = super.unmarshalled(generator);
        
        // unwrap fragment elements and propagate
        // unmarshalled notification
        fragments.clear();
        Iterator fragmentElementIter = fragmentElementImpls.iterator();
        while (fragmentElementIter.hasNext())
        {
            // unwrap fragment element
            FragmentElementImpl fragmentElement = (FragmentElementImpl)fragmentElementIter.next();
            AbstractBaseFragmentElement fragment = (AbstractBaseFragmentElement)fragmentElement.getElement();
            fragments.add(fragment);
            
            // propagate unmarshalled notification
            dirty = fragment.unmarshalled(generator) || dirty;
        }

        return dirty;
    }

    /**
     * marshalling - notification that this instance is to
     *               be saved to the persistent store
     */
    public void marshalling()
    {
        // wrap menu elements and propagate
        // marshalling notification
        fragmentElementImpls.clear();
        Iterator fragmentIter = fragments.iterator();
        while (fragmentIter.hasNext())
        {
            // wrap fragment element
            AbstractBaseFragmentElement fragment = (AbstractBaseFragmentElement)fragmentIter.next();
            fragmentElementImpls.add(new FragmentElementImpl(fragment));

            // propagate marshalling notification
            fragment.marshalling();
        }

        // notify super class implementation
        super.marshalling();
    }

    /**
     * filterFragmentsByAccess
     *
     * Filter fragments list for view access.
     *
     * @param nodes list containing fragments to check
     * @return original list if all elements viewable, a filtered
     *         partial list, or null if all filtered for view access
     */
    List filterFragmentsByAccess(List fragments)
    {
        if ((fragments != null) && !fragments.isEmpty())
        {
            // check permissions and constraints, filter fragments as required
            List filteredFragments = null;
            Iterator checkAccessIter = fragments.iterator();
            while (checkAccessIter.hasNext())
            {
                BaseFragmentElement fragment = (BaseFragmentElement) checkAccessIter.next();
                try
                {
                    // check access
                    fragment.checkAccess(JetspeedActions.VIEW);

                    // add to filteredFragments fragments if copying
                    if (filteredFragments != null)
                    {
                        // permitted, add to filteredFragments fragments
                        filteredFragments.add(fragment);
                    }
                }
                catch (SecurityException se)
                {
                    // create filteredFragments fragments if not already copying
                    if (filteredFragments == null)
                    {
                        // not permitted, copy previously permitted fragments
                        // to new filteredFragments node set with same comparator
                        filteredFragments = new ArrayList(fragments.size());
                        Iterator copyIter = fragments.iterator();
                        while (copyIter.hasNext())
                        {
                            BaseFragmentElement copyFragment = (BaseFragmentElement)copyIter.next();
                            if (copyFragment != fragment)
                            {
                                filteredFragments.add(copyFragment);
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                }
            }

            // return filteredFragments fragments if generated
            if (filteredFragments != null)
            {
                // patch for JS2-633, security filtered (permission) lists
                // were returning null, we need an empty fragment list 
                return new FilteredFragmentList(this, filteredFragments);
            }
        }
        return fragments;
    }
}
