/*
 * 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.impl;

import java.util.Iterator;
import java.util.List;

import org.apache.jetspeed.JetspeedActions;
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;
import org.apache.jetspeed.page.impl.DatabasePageManagerUtils;

/**
 * FragmentImpl
 *
 * @author <a href="mailto:rwatler@apache.org">Randy Watler</a>
 * @version $Id$
 */
public class FragmentImpl extends BaseFragmentElementImpl implements Fragment
{
    private List fragments;
    private String type;

    private FragmentList fragmentsList;

    /**
     * accessFragments
     *
     * Access mutable persistent collection member for List wrappers.
     *
     * @return persistent collection
     */
    List accessFragments()
    {
        // create initial collection if necessary
        if (fragments == null)
        {
            fragments = DatabasePageManagerUtils.createList();
        }
        return fragments;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.impl.BaseFragmentElementImpl#setBaseFragmentsElement(org.apache.jetspeed.om.page.impl.BaseFragmentsElementImpl)
     */
    void setBaseFragmentsElement(BaseFragmentsElementImpl baseFragmentsElement)
    {
        // set page implementation
        super.setBaseFragmentsElement(baseFragmentsElement);
        // propagate to children
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                ((BaseFragmentElementImpl)fragmentsIter.next()).setBaseFragmentsElement(baseFragmentsElement);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.Fragment#getFragmentById(java.lang.String)
     * @see org.apache.jetspeed.om.page.impl.BaseFragmentElementImpl#getFragmentById(java.lang.String)
     */
    public BaseFragmentElement getFragmentById(String id)
    {
        // check for match
        if (getId().equals(id))
        {
            return this;
        }
        // match children
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                BaseFragmentElement matchedFragment = ((BaseFragmentElementImpl)fragmentsIter.next()).getFragmentById(id);
                if (matchedFragment != null)
                {
                    return matchedFragment;
                }
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.Fragment#removeFragmentById(java.lang.String)
     */
    public BaseFragmentElement removeFragmentById(String id)
    {
        // remove from deep children
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                BaseFragmentElementImpl fragment = (BaseFragmentElementImpl)fragmentsIter.next();
                if (!fragment.getId().equals(id))
                {
                    if (fragment instanceof FragmentImpl)
                    {
                        BaseFragmentElement removed = ((FragmentImpl)fragment).removeFragmentById(id);
                        if (removed != null)
                        {
                            return removed;
                        }
                    }
                }
                else
                {
                    try
                    {
                        // check access
                        fragment.checkAccess(JetspeedActions.EDIT);
                        
                        // remove fragment
                        fragmentsIter.remove();
                        return fragment;
                    }
                    catch (SecurityException se)
                    {
                    }
                }
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.impl.BaseFragmentElementImpl#getFragmentsByName(java.lang.String)
     */
    List getFragmentsByName(String name)
    {
        // check for match
        List matchedFragments = super.getFragmentsByName(name);
        // match named children
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                List matchedChildFragments = ((BaseFragmentElementImpl)fragmentsIter.next()).getFragmentsByName(name);
                if (matchedChildFragments != null)
                {
                    if (matchedFragments == null)
                    {
                        matchedFragments = matchedChildFragments;
                    }
                    else
                    {
                        matchedFragments.addAll(matchedChildFragments);
                    }
                }
            }
        }
        return matchedFragments;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.impl.BaseFragmentElementImpl#getFragmentsByInterface(java.lang.Class)
     */
    List getFragmentsByInterface(Class interfaceFilter)
    {
        // check for match
        List matchedFragments = super.getFragmentsByInterface(interfaceFilter);
        // match children
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                List matchedChildFragments = ((BaseFragmentElementImpl)fragmentsIter.next()).getFragmentsByInterface(interfaceFilter);
                if (matchedChildFragments != null)
                {
                    if (matchedFragments == null)
                    {
                        matchedFragments = matchedChildFragments;
                    }
                    else
                    {
                        matchedFragments.addAll(matchedChildFragments);
                    }
                }
            }
        }
        return matchedFragments;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.impl.BaseElementImpl#resetCachedSecurityConstraints()
     */
    public void resetCachedSecurityConstraints()
    {
        // propagate to super and sub fragments
        super.resetCachedSecurityConstraints();
        if (fragments != null)
        {
            Iterator fragmentsIter = fragments.iterator();
            while (fragmentsIter.hasNext())
            {
                ((BaseFragmentElementImpl)fragmentsIter.next()).resetCachedSecurityConstraints();
            }
        }
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.impl.BaseElementImpl#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.impl.BaseElementImpl#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.Fragment#getType()
     */
    public String getType()
    {
        return type;
    }
    
    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.Fragment#setType(java.lang.String)
     */
    public void setType(String type)
    {
        this.type = type;
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.Fragment#getFragments()
     */
    public List getFragments()
    {
        // create and return mutable fragments collection
        // filtered by view access
        if (fragmentsList == null)
        {
            fragmentsList = new FragmentList(this);
        }
        return filterFragmentsByAccess(fragmentsList, true);
    }

    /* (non-Javadoc)
     * @see org.apache.jetspeed.om.page.impl.BaseFragmentElementImpl#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 (!((BaseFragmentElementImpl)fragmentsIter.next()).validateFragments(validationListener))
                {
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * filterFragmentsByAccess
     *
     * Filter fragments list for view access.
     *
     * @param nodes list containing fragments to check
     * @param mutable make returned list mutable
     * @return original list if all elements viewable, a filtered
     *         partial list, or null if all filtered for view access
     */
    List filterFragmentsByAccess(List fragments, boolean mutable)
    {
        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 = DatabasePageManagerUtils.createList();
                        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)
            {
                if (!filteredFragments.isEmpty())
                {
                    if (mutable)
                    {
                        return new FilteredFragmentList(this, filteredFragments);
                    }
                    else
                    {
                        return filteredFragments;
                    }
                }
                else
                {
                    return new FilteredFragmentList(this, filteredFragments);
                }
            }
        }
        return fragments;
    }
}
