All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.milton.http.json.JsonPropFindHandler Maven / Gradle / Ivy

Go to download

Milton Community Edition: Supports DAV level 1 and is available on Apache2 license

The newest version!
/*
 * 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 io.milton.http.json;

import io.milton.common.LogUtils;
import io.milton.http.Range;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.values.ValueAndType;
import io.milton.http.webdav.PropFindPropertyBuilder;
import io.milton.http.webdav.PropFindResponse;
import io.milton.http.webdav.PropertiesRequest;
import io.milton.http.webdav.PropertiesRequest.Property;
import io.milton.http.webdav.WebDavProtocol;
import io.milton.resource.CollectionResource;
import io.milton.resource.PropFindableResource;
import io.milton.resource.Resource;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.util.*;
import java.util.Map.Entry;

import javax.xml.namespace.QName;

import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
import net.sf.json.JsonConfig;
import net.sf.json.util.CycleDetectionStrategy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author brad
 */
public class JsonPropFindHandler {

    private static final Logger log = LoggerFactory.getLogger(JsonPropFindHandler.class);
    private final PropFindPropertyBuilder propertyBuilder;
    private final Helper helper;

    public JsonPropFindHandler(PropFindPropertyBuilder propertyBuilder) {
        this.propertyBuilder = propertyBuilder;
        helper = new Helper();
    }

    public void sendContent(PropFindableResource wrappedResource, String encodedUrl, OutputStream out, Range range, Map params, String contentType) throws IOException, NotAuthorizedException, BadRequestException {
        log.debug("sendContent: " + encodedUrl);
        JsonConfig cfg = new JsonConfig();
        cfg.setIgnoreTransientFields(true);
        cfg.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);

        JSON json;
        Writer writer = new PrintWriter(out);
        String[] arr;
        if (propertyBuilder == null) {
            if (wrappedResource instanceof CollectionResource) {
                List children = Optional.ofNullable(((CollectionResource) wrappedResource).getChildren()).orElse(List.of());
                json = JSONSerializer.toJSON(toSimpleList(children), cfg);
            } else {
                json = JSONSerializer.toJSON(toSimple(wrappedResource), cfg);
            }
        } else {
            // use propfind handler
            String sFields = params.get("fields");
            Set fields = new HashSet<>();
            Map aliases = new HashMap<>();
            if (sFields != null && sFields.length() > 0) {
                arr = sFields.split(",");
                for (String s : arr) {
                    parseField(s, fields, aliases);
                }
            }

            String sDepth = params.get("depth");
            int depth = 1;
            if (sDepth != null && sDepth.trim().length() > 0) {
                depth = Integer.parseInt(sDepth);
            }


            String href = encodedUrl.replace("/_DAV/PROPFIND", "");
            PropertiesRequest parseResult = new PropertiesRequest(toProperties(fields));
            LogUtils.debug(log, "prop builder: ", propertyBuilder.getClass(), "href", href);
            List props;
			try {
				props = propertyBuilder.buildProperties(wrappedResource, depth, parseResult, href);
			} catch (URISyntaxException ex) {
				throw new RuntimeException("Requested url is not properly encoded: " + href, ex);
			}

            String where = params.get("where");
            filterResults(props, where);

            List> list = helper.toMap(props, aliases);
            json = JSONSerializer.toJSON(list, cfg);
        }
        json.write(writer);
        writer.flush();
    }

	private Set toProperties(Set set) {
		Set props = new HashSet<>();
		for(QName n : set ) {
			props.add(new Property(n, null));
		}
		return props;
	}

    /**
     * Parse the given field and populate the given maps
     *
     * A field may be in the following forms
     * - foo
     * - DAV:foo
     * - DAV:foo>bar
     * - foo>bar
     *
     * The first is just a property named foo.
     * The second is a property called foo in the namespace DAV
     * The third includes an alias so the property is returned with the name "bar" in the json object
     * The final shows that an alias can be used without a namespace
     *
     * @param field
     * @param fields
     */
    void parseField(String field, Set fields, Map aliases) {
        String alias = null;
        if (field.contains(">")) {
            int pos = field.indexOf(">");
            alias = field.substring(pos + 1);
            field = field.substring(0, pos);
        }
        QName qn = parseQName(field);
        //log.debug("field: " + qn);
        fields.add(qn);
        if (alias != null) {
            aliases.put(qn, alias);
        }
    }

    private QName parseQName(String field) {
        if (field.contains(":")) {
            // name is of form uri:local  E.g. MyDav:authorName
            String[] parts = field.split(":");
            String nsUri = parts[0];
            String localName = parts[1];
            return new QName(nsUri, localName);
        } else {
            // name is simple form E.g. displayname, default nsUri to DAV
            return new QName(WebDavProtocol.NS_DAV.getName(), field);
        }

    }

    private List toSimpleList(List list) {
        List simpleList = new ArrayList<>(list.size());
        for (Resource r : list) {
            simpleList.add(toSimple(r));
        }
        return simpleList;
    }

    private SimpleResource toSimple(Resource r) {
        return new SimpleResource(r);
    }

    /**
     * If the where argument is given, removes results where it does not
     * evaluate to true
     *
     * If the given where argument starts with ! the condition is negated
     *
     * @param results
     * @param where
     */
    private void filterResults(List results, String where) {
        if (where != null && where.length() > 0) {
            boolean negate = where.startsWith("!");
            if (negate) {
                where = where.substring(1);
            }
            ValueAndType prop;
            QName qnWhere = parseQName(where);
            Iterator it = results.iterator();
            while (it.hasNext()) {
                PropFindResponse result = it.next();
                boolean isTrue = eval(qnWhere, result);
                // eg !iscollection for a folder -> false == false = true, so remove
                // eg !iscollection for a file -> true == false = false, dont remove
                // eg iscollection for a folder -> false == true = false, so dont remove
                if (isTrue == negate) {
                    it.remove();
                }
            }
        }
    }

    /**
     * Find a boolean value from the given propery name on the propfind
     * result.
     *
     * Absense of the property, or a value which
     * cannot be interpreted as boolean, implies false.
     *
     * @param qnWhere
     * @param result
     * @return
     */
    private boolean eval(QName qnWhere, PropFindResponse result) {
        ValueAndType prop = result.getKnownProperties().get(qnWhere);
        if (prop != null) {
            Object val = prop.getValue();
            if (val instanceof Boolean) {
                return (Boolean) val;
            } else {
                return false;
            }
        } else {
            return false;
        }

    }

    public static class SimpleResource {

        private final Resource r;

        public SimpleResource(Resource r) {
            this.r = r;
        }

        public String getName() {
            return r.getName();
        }

        public Date getModifiedDate() {
            return r.getModifiedDate();
        }
    }

    static class Helper {

        private List> toMap(List props, Map aliases) {
            List> list = new ArrayList<>();
            Object val;
            for (PropFindResponse prop : props) {
                Map map = new HashMap<>();
                list.add(map);
                for (Entry p : prop.getKnownProperties().entrySet()) {
                    String name = aliases.get(p.getKey());
                    if (name == null) {
                        name = p.getKey().getLocalPart();
                    }
                    val = p.getValue().getValue();
                    map.put(name, val);
                }
            }
            return list;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy