/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/wvcm/src/org/apache/wvcm/store/webdav/response/PropertiesFactory.java,v 1.5 2004/12/03 14:27:40 pnever Exp $
 * $Revision: 1.5 $
 * $Date: 2004/12/03 14:27:40 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.wvcm.store.webdav.response;
import java.util.*;
import javax.wvcm.*;
import org.apache.wvcm.*;
import org.jdom.*;

import java.io.IOException;
import java.io.StringBufferInputStream;
import java.io.StringWriter;
import javax.wvcm.PropertyNameList.AttributeName;
import javax.wvcm.PropertyNameList.NestedPropertyName;
import javax.wvcm.PropertyNameList.PropertyName;
import javax.wvcm.WvcmException.ReasonCode;
import org.apache.wvcm.model.PropertyDescriptor;
import org.apache.wvcm.store.webdav.DateRepresentations;
import org.apache.wvcm.store.webdav.WebdavProperties;
import org.apache.wvcm.util.PropertyNameLists;
import org.apache.wvcm.util.XPathWrapper;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

/**
 * Creates the properties out of XML DAV:multistatus response elements.
 *
 * @author <a href="mailto:peter.nevermann@softwareag.com">Peter Nevermann</a>
 * @version $Revision: 1.5 $
 */
public class PropertiesFactory {
    
    private static Namespace dnsp = Namespace.getNamespace("d", "DAV:");
    
    private Resource requestResource;
    private Provider provider;
    private PropertyNameList wantedPropertyList;
    private Map propertiesContainer;
    private boolean allAttributes = false;
    
    public PropertiesFactory(Resource requestResource, Provider provider, PropertyNameList wantedPropertyList, Map propertiesContainer) {
        this.requestResource = requestResource;
        this.provider = provider;
        this.wantedPropertyList = wantedPropertyList;
        this.propertiesContainer = propertiesContainer;
        if (wantedPropertyList != null && wantedPropertyList.getPropertyNames().length == 1) {
            if (wantedPropertyList.getPropertyNames()[0] == PropertyName.ALL_ATTRIBUTES) {
                allAttributes = true;
            }
        }
    }
    
    public void create(Element propertyElm) throws WvcmException, NumberFormatException {
        XPathWrapper xp;
        PropertyName propname = null;
        if ("DAV:".equals(propertyElm.getNamespace().getURI())) {
            propname =
                WebdavProperties.propertyNameForWebdavName( propertyElm.getName() );
        }
        String namespace = "DAV:";
        if( propname == null ) {
            Namespace ns = propertyElm.getNamespace();
            // TODO: check with wanted properties whether to take-over or not
            propname = new AttributeName( ns.getURI(), propertyElm.getName() );
            namespace = ns.getURI();
        }
        
        // get the wanted PropertyName
        PropertyName wantedPropName =
            PropertyNameLists.propertyName(wantedPropertyList, propname.getString(), namespace);
        
        if (!allAttributes) {
            if (wantedPropName == null) {
                return;
            }
        }
        else if (!(propname instanceof AttributeName)) {
            return;
        }
        
        PropertyDescriptor propdescriptor = null;
        if( propname instanceof AttributeName )
            propdescriptor = PropertyDescriptor.getInstance((AttributeName)propname, propertyElm);
        else
            propdescriptor = PropertyDescriptor.getInstance(propname);
        
        Object propvalue = null;
        if( propdescriptor != null ) {
            if( propdescriptor.isHref() ) {
                // this is a href property
                boolean multipleValued = false;
                if( propdescriptor.collectiontypeInstance() != null ) {
                    multipleValued = true;
                    propvalue = propdescriptor.collectiontypeInstance();
                }
                xp = new XPathWrapper( "d:response", dnsp );
                List respelms = xp.selectNodes(propertyElm);
                if( respelms.size() > 0 ) {
                    // nested: there are response elements inside p
                    Iterator j = respelms.iterator();
                    while( j.hasNext() ) {
                        Element re2 = (Element)j.next();
                        PropertyNameList newWantedPnameList = null;
                        if (wantedPropName instanceof NestedPropertyName) {
                            newWantedPnameList = ((NestedPropertyName)wantedPropName).getNested();
                        }
                        ResourceProxyFactory proxyFactory =
                            new ResourceProxyFactory(null, provider, newWantedPnameList);
                        if( multipleValued ) {
                            ((Collection)propvalue).add( proxyFactory.create(re2, propdescriptor.basetype()) );
                        }
                        else {
                            propvalue = proxyFactory.create( re2, propdescriptor.basetype() );
                        }
                    }
                }
                else {
                    // not further nested: get the href
                    XPathWrapper xp1 = new XPathWrapper( "d:href", dnsp );
                    List hrefs = xp1.selectNodes(propertyElm); // href strings
                    LocationImpl l;
                    try {
                        if( multipleValued ) {
                            Iterator j = hrefs.iterator();
                            while( j.hasNext() ) {
                                Element hrefElm = (Element)j.next();
                                l = (LocationImpl)provider.location( hrefElm.getText() );
                                ((Collection)propvalue).add( (ResourceImpl)l.resource(propdescriptor.basetype()) );
                            }
                        }
                        else {
                            Element hrefElm = (Element)hrefs.get(0);
                            l = (LocationImpl)provider.location( hrefElm.getText() );
                            propvalue = (ResourceImpl)l.resource( propdescriptor.basetype() );
                        }
                    }
                    catch (Exception e) {
                        // could not extract href
                        propvalue = null;
                    }
                }
            }
            else if( propdescriptor.basetype() == ResourceImpl.XmlPropertyValue.class ) {
                Format f = Format.getPrettyFormat();
                f.setEncoding("UTF-8");
                propvalue = new ResourceImpl.XmlPropertyValue(
                    requestResource,
                    //JDOMbeta                    new XMLOutputter("", false, "UTF-8").outputString(propertyElm)
                    new XMLOutputter(f).outputString(propertyElm)
                );
            }
            else if( propdescriptor.basetype() == FolderImpl.BindingImpl.class ) {
                propvalue = propdescriptor.collectiontypeInstance();
                Iterator iter = propertyElm.getChildren("parent", dnsp).iterator();
                LocationImpl l;
                while (iter.hasNext()) {
                    Element pElm = (Element)iter.next();
                    Element segmElm = pElm.getChild("segment", dnsp);
                    Element hrefElm = pElm.getChild("href", dnsp);
                    String bn = segmElm.getText().trim();
                    l = (LocationImpl)provider.location( hrefElm.getText().trim() );
                    ((Collection)propvalue).add( new FolderImpl.BindingImpl(provider, bn, (ControllableFolder)l.resource(ControllableFolderImpl.class)) );
                }
            }
            else if( propdescriptor.basetype() == String.class ) {
                if( propdescriptor.collectiontype() == null ) {
                    propvalue = propertyElm.getTextTrim();
                }
                else {
                    propvalue = propdescriptor.collectiontypeInstance();
                    Iterator iter = propertyElm.getChildren().iterator();
                    while (iter.hasNext()) {
                        Element c = (Element)iter.next();
                        ((Collection)propvalue).add( c.getTextTrim() );
                    }
                }
            }
            else if( propdescriptor.basetype() == Date.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = DateRepresentations.parseDate( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Boolean.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Boolean( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Locale.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = WebdavProperties.languageTagToLocale((String)propvalue);
            }
            else if( propdescriptor.basetype() == Long.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Long( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Integer.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Integer( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Short.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Short( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Byte.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Byte( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Character.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Character( ((String)propvalue).charAt(0) );
            }
            else if( propdescriptor.basetype() == Double.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Double( (String)propvalue );
            }
            else if( propdescriptor.basetype() == Float.class && propdescriptor.collectiontype() == null ) {
                propvalue = propertyElm.getTextTrim();
                propvalue = new Float( (String)propvalue );
            }
            
            if (propname.getString().endsWith("privilege-list")) {
                propvalue = createSupportedPrivilegeList(propertyElm);
            }
        }
        else {
            Iterator k = propertyElm.getContent().iterator();
            
            if (propertyElm.getContent().size() == 1 && propertyElm.getContent().get(0) instanceof Text) {
                propvalue = propertyElm.getText();
            }
            else {
                StringWriter writer = new StringWriter();
                while( k.hasNext() ) {
                    Object o = k.next();
                    try {
                        if( o instanceof Element )
                            new XMLOutputter().output((Element)o, writer);
                        else if( o instanceof Text )
                            new XMLOutputter().output((Text)o, writer);
                        else if( o instanceof Comment )
                            new XMLOutputter().output((Comment)o, writer);
                        else if( o instanceof ProcessingInstruction )
                            new XMLOutputter().output((ProcessingInstruction)o, writer);
                        else if( o instanceof CDATA )
                            new XMLOutputter().output((CDATA)o, writer);
                        else if( o instanceof EntityRef )
                            new XMLOutputter().output((EntityRef)o, writer);
                    }
                    catch (IOException e) {}
                }
                propvalue = writer.toString().trim();
            }
            
            if (propname.getString().endsWith("privilege-set")) {
                propvalue = createPrivilegeList((String)propvalue);
            }
            else if ("principal-has-privilege".equals(propname.getString())) {
                String xmlString = (String)propvalue;
                if (xmlString.indexOf("grant") >= 0) {
                    propvalue = Boolean.TRUE;
                }
                else {
                    propvalue = Boolean.FALSE;
                }
            }
        }
        
        propertiesContainer.put( propname, propvalue );
        
        // special handling for non-WebDAV properties
        if( propname == PropertyName.CHECKED_OUT && propertiesContainer.containsKey(PropertyName.IS_CHECKED_OUT)) {
            propertiesContainer.put( PropertyName.IS_CHECKED_OUT, new Boolean(true) );
        }
        else if( propname == PropertyName.CHECKED_IN && propertiesContainer.containsKey(PropertyName.IS_CHECKED_OUT)) {
            propertiesContainer.put( PropertyName.IS_CHECKED_OUT, new Boolean(false) );
        }
        else if( propname == PropertyName.CONTENT_TYPE ) {
            String[] ctTokens = tokenizeContentType( (String)propvalue );
            if (propertiesContainer.containsKey(PropertyName.CONTENT_TYPE)) {
                propertiesContainer.put( PropertyName.CONTENT_TYPE, ctTokens[0] );
            }
            if (propertiesContainer.containsKey(PropertyName.CONTENT_CHARACTER_SET)) {
                propertiesContainer.put( PropertyName.CONTENT_CHARACTER_SET, ctTokens[1] );
            }
        }
    }
    
    private List createPrivilegeList(String xmlPrivileges) throws WvcmException {
        List privileges = new ArrayList();
        try {
            Document d = ProviderImpl.getSAXBuilder()
                .build(new StringBufferInputStream("<R>"+xmlPrivileges+"</R>"));
            Iterator i = d.getRootElement().getChildren("privilege", dnsp).iterator();
            while (i.hasNext()) {
                Element pElm = (Element)((Element)i.next()).getChildren().get(0);
                Location privilegesLocation = ((ProviderImpl)provider).serverPrivilegesLocation();
                LocationImpl l = (LocationImpl)privilegesLocation.child(pElm.getName());
                privileges.add( l.resource(PrivilegeImpl.class) );
            }
        }
        catch (JDOMException e) {
            throw new WvcmException("", requestResource.location().string(), ReasonCode.READ_FAILED, new Exception[]{e});
        }
        catch (IOException e) {
            e.printStackTrace();
            throw new WvcmException("", requestResource.location().string(), ReasonCode.READ_FAILED, new Exception[]{e});
        }
        return privileges;
    }
    
    private List createSupportedPrivilegeList(Element propertyElm) throws WvcmException {
        List privileges = new ArrayList();
        XPathWrapper xp = new XPathWrapper("//d:privilege", dnsp);
        Iterator i = xp.selectNodes(propertyElm).iterator();
        while (i.hasNext()) {
            Element pElm = (Element)((Element)i.next()).getChildren().get(0);
            Location privilegesLocation = ((ProviderImpl)provider).serverPrivilegesLocation();
            if ("all".equals(pElm.getName())) {
                privileges.add(AccessControlElement.Privilege.ALL);
            }
            else {
                LocationImpl l = (LocationImpl)privilegesLocation.child(pElm.getName());
                privileges.add( l.resource(PrivilegeImpl.class) );
            }
        }
        return privileges;
    }
    
    private String[] tokenizeContentType( String propvalue ) {
        String[] result = new String[]{"",""};
        if( propvalue == null || propvalue.length() == 0 )
            return result;
        
        // parsing something like: text/plain; charset="utf-8"
        StringTokenizer t = new StringTokenizer( propvalue, ";\"" );
        try {
            result[0] = t.nextToken(";");
            t.nextToken("\"");
            result[1] = t.nextToken("\"");
        } catch (NoSuchElementException e) {} // ignore silently
        
        return result;
    }
}


