io.personium.core.model.impl.fs.DavCmpFsImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.personium.core.model.impl.fs.DavCmpFsImpl.java

Source

/**
 * personium.io
 * Copyright 2014 FUJITSU LIMITED
 *
 * Licensed 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.personium.core.model.impl.fs;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.StreamingOutput;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.FileUtils;
import org.apache.http.HttpStatus;
import org.apache.wink.webdav.model.Multistatus;
import org.apache.wink.webdav.model.ObjectFactory;
import org.apache.wink.webdav.model.Prop;
import org.apache.wink.webdav.model.Propertyupdate;
import org.apache.wink.webdav.model.Response;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.odata4j.producer.CountResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import io.personium.common.auth.token.Role;
import io.personium.common.es.response.PersoniumGetResponse;
import io.personium.common.es.util.IndexNameEncoder;
import io.personium.common.utils.PersoniumCoreUtils;
import io.personium.core.PersoniumCoreException;
import io.personium.core.PersoniumCoreLog;
import io.personium.core.PersoniumUnitConfig;
import io.personium.core.auth.AccessContext;
import io.personium.core.auth.BoxPrivilege;
import io.personium.core.auth.OAuth2Helper.Key;
import io.personium.core.http.header.ByteRangeSpec;
import io.personium.core.http.header.RangeHeaderHandler;
import io.personium.core.model.Box;
import io.personium.core.model.Cell;
import io.personium.core.model.DavCmp;
import io.personium.core.model.DavDestination;
import io.personium.core.model.ModelFactory;
import io.personium.core.model.ctl.ComplexType;
import io.personium.core.model.ctl.EntityType;
import io.personium.core.model.file.BinaryDataAccessor;
import io.personium.core.model.file.BinaryDataNotFoundException;
import io.personium.core.model.file.CipherInputStream;
import io.personium.core.model.file.DataCryptor;
import io.personium.core.model.file.StreamingOutputForDavFile;
import io.personium.core.model.file.StreamingOutputForDavFileWithRange;
import io.personium.core.model.impl.es.EsModel;
import io.personium.core.model.impl.es.accessor.EntitySetAccessor;
import io.personium.core.model.impl.es.odata.UserSchemaODataProducer;
import io.personium.core.model.jaxb.Ace;
import io.personium.core.model.jaxb.Acl;
import io.personium.core.model.jaxb.ObjectIo;
import io.personium.core.model.lock.Lock;
import io.personium.core.model.lock.LockKeyComposer;
import io.personium.core.model.lock.LockManager;
import io.personium.core.odata.PersoniumODataProducer;

/**
 * DavCmp implementation using FileSystem.
 */
public class DavCmpFsImpl implements DavCmp {
    String fsPath;
    File fsDir;

    Box box;
    Cell cell;
    ObjectFactory of;

    String name;
    Acl acl;
    DavMetadataFile metaFile;
    DavCmpFsImpl parent;
    List<String> ownerRepresentativeAccounts = new ArrayList<String>();
    boolean isPhantom = false;

    /**
     * Fixed File Name for storing file.
     */
    private static final String CONTENT_FILE_NAME = "content";
    private static final String TEMP_FILE_NAME = "tmp";

    /*
     * logger.
     */
    private static Logger log = LoggerFactory.getLogger(DavCmpFsImpl.class);

    DavCmpFsImpl() {
    }

    /**
     * constructor.
     * @param name
     *            name of the path component
     * @param parent
     *            parent DavCmp object
     * @param cell
     *            Cell
     * @param box
     *            Box
     */
    private DavCmpFsImpl(final String name, final DavCmpFsImpl parent) {
        this.name = name;
        this.of = new ObjectFactory();

        this.parent = parent;

        if (parent == null) {
            this.metaFile = DavMetadataFile.newInstance(this);
            return;
        }
        this.cell = parent.getCell();
        this.box = parent.getBox();
        this.fsPath = this.parent.fsPath + File.separator + this.name;
        this.fsDir = new File(this.fsPath);

        this.metaFile = DavMetadataFile.newInstance(this);
    }

    /**
     * create a DavCmp whose path most probably does not yet exist.
     * There still are possibilities that other thread creates the corresponding resource and
     * the path actually exists.
     * @param name path name
     * @param parent parent DavCmp
     * @return created DavCmp
     */
    public static DavCmpFsImpl createPhantom(final String name, final DavCmpFsImpl parent) {
        DavCmpFsImpl ret = new DavCmpFsImpl(name, parent);
        ret.isPhantom = true;
        return ret;
    }

    /**
     * create a DavCmp whose path most probably does exist.
     * There still are possibilities that other thread deletes the corresponding resource and
     * the path actually does not exists.
     * @param name path name
     * @param parent parent DavCmp
     * @return created DavCmp
     */
    public static DavCmpFsImpl create(final String name, final DavCmpFsImpl parent) {
        DavCmpFsImpl ret = new DavCmpFsImpl(name, parent);
        if (ret.exists()) {
            ret.load();
        } else {
            ret.isPhantom = true;
        }
        return ret;
    }

    void createNewMetadataFile() {
        this.metaFile = DavMetadataFile.prepareNewFile(this, this.getType());
        this.metaFile.save();
    }

    @Override
    public boolean isEmpty() {
        if (!this.exists()) {
            return true;
        }
        String type = this.getType();
        if (DavCmp.TYPE_COL_WEBDAV.equals(type)) {
            return !(this.getChildrenCount() > 0);
        } else if (DavCmp.TYPE_COL_BOX.equals(type)) {
            return !(this.getChildrenCount() > 0);
        } else if (DavCmp.TYPE_COL_ODATA.equals(type)) {
            // Collection?????EntityType???
            // EntityType?????(AsssociationEnd??)?EntityType????????
            // EntityType?????EntityType?????????????
            UserSchemaODataProducer producer = new UserSchemaODataProducer(this.cell, this);
            CountResponse cr = producer.getEntitiesCount(EntityType.EDM_TYPE_NAME, null);
            if (cr.getCount() > 0) {
                return false;
            }
            // Collection?????ComplexType???
            // ComplexType?????(ComplexTypeProperty)?ComplexType????????
            // ComplexType?????ComplexType?????????????
            cr = producer.getEntitiesCount(ComplexType.EDM_TYPE_NAME, null);
            return cr.getCount() < 1;
        } else if (DavCmp.TYPE_COL_SVC.equals(type)) {
            DavCmp svcSourceCol = this.getChild(SERVICE_SRC_COLLECTION);
            if (!svcSourceCol.exists()) {
                // ??Service????
                // ServiceSource??????????
                return true;
            }
            return !(svcSourceCol.getChildrenCount() > 0);
        }
        PersoniumCoreLog.Misc.UNREACHABLE_CODE_ERROR.writeLog();
        throw PersoniumCoreException.Server.UNKNOWN_ERROR;
    }

    @Override
    public void makeEmpty() {
        // TODO Impl
    }

    /**
     * @return Acl
     */
    public Acl getAcl() {
        return this.acl;
    }

    /**
     * ???.
     * @return ?
     */
    public String getConfidentialLevel() {
        if (acl == null) {
            return null;
        }
        return this.acl.getRequireSchemaAuthz();
    }

    /**
     * ??.
     * @return ?
     */
    public List<String> getOwnerRepresentativeAccounts() {
        return this.ownerRepresentativeAccounts;
    }

    /**
     * Box?.
     * @return ?
     */
    public Lock lock() {
        log.debug("lock:"
                + LockKeyComposer.fullKeyFromCategoryAndKey(Lock.CATEGORY_DAV, null, this.box.getId(), null));
        return LockManager.getLock(Lock.CATEGORY_DAV, null, this.box.getId(), null);
    }

    /**
     * @return ETag String with double quote signs.
     */
    @Override
    public String getEtag() {
        StringBuilder sb = new StringBuilder("\"");
        sb.append(this.metaFile.getVersion());
        sb.append("-");
        sb.append(this.metaFile.getUpdated());
        sb.append("\"");
        return sb.toString();
    }

    /**
     * checks if this cmp is Cell level.
     * @return true if Cell level
     */
    public boolean isCellLevel() {
        return false;
    }

    void createDir() {
        try {
            Files.createDirectories(this.fsDir.toPath());
        } catch (IOException e) {
            // Failed to create directory.
            throw new RuntimeException(e);
        }
    }

    /**
     * returns if this resource exists.<br />
     * before using this method, do not forget to load() and update the info.
     * @return true if this resource should exist
     */
    @Override
    public final boolean exists() {
        return this.fsDir != null && this.fsDir.exists() && this.metaFile.exists();
    }

    /**
     * load the info from FS for this Dav resouce.
     */
    public final void load() {
        this.metaFile.load();

        /*
         * Analyze JSON Object, and set metadata such as ACL.
         */
        this.name = fsDir.getName();
        this.acl = this.translateAcl(this.metaFile.getAcl());

        @SuppressWarnings("unchecked")
        Map<String, String> props = (Map<String, String>) this.metaFile.getProperties();
        if (props != null) {
            for (Map.Entry<String, String> entry : props.entrySet()) {
                String key = entry.getKey();
                String val = entry.getValue();
                int idx = key.indexOf("@");
                String elementName = key.substring(0, idx);
                String namespace = key.substring(idx + 1);
                QName keyQName = new QName(namespace, elementName);

                Element element = parseProp(val);
                String elementNameSpace = element.getNamespaceURI();
                // ownerRepresentativeAccounts???
                if (Key.PROP_KEY_OWNER_REPRESENTIVE_ACCOUNTS.equals(keyQName)) {
                    NodeList accountNodeList = element.getElementsByTagNameNS(elementNameSpace,
                            Key.PROP_KEY_OWNER_REPRESENTIVE_ACCOUNT.getLocalPart());
                    for (int i = 0; i < accountNodeList.getLength(); i++) {
                        this.ownerRepresentativeAccounts.add(accountNodeList.item(i).getTextContent().trim());
                    }
                }
            }
        }
    }

    /**
     * Dav???.<br />
     * ?????????.
     */
    public final void loadAndCheckDavInconsistency() {
        load();
        if (this.metaFile == null) {
            // Box???id????Dav????????
            throw PersoniumCoreException.Dav.DAV_INCONSISTENCY_FOUND;
        }
    }

    private Element parseProp(String value) {
        // valDOM?Element
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = null;
        Document doc = null;
        try {
            builder = factory.newDocumentBuilder();
            ByteArrayInputStream is = new ByteArrayInputStream(value.getBytes(CharEncoding.UTF_8));
            doc = builder.parse(is);
        } catch (Exception e1) {
            throw PersoniumCoreException.Dav.DAV_INCONSISTENCY_FOUND.reason(e1);
        }
        Element e = doc.getDocumentElement();
        return e;
    }

    /*
     * proppatch ??. ??? key = namespaceUri + "@" + localName Value =
     * inner XML String
     */
    @Override
    @SuppressWarnings("unchecked")
    public Multistatus proppatch(final Propertyupdate propUpdate, final String url) {
        long now = new Date().getTime();
        String reqUri = url;
        Multistatus ms = this.of.createMultistatus();
        Response res = this.of.createResponse();
        res.getHref().add(reqUri);

        // Lock
        Lock lock = this.lock();
        // ?
        try {
            this.load(); // ??

            if (!this.exists()) {
                // ?(??)?????404??
                throw getNotFoundException().params(this.getUrl());
            }

            Map<String, Object> propsJson = (Map<String, Object>) this.metaFile.getProperties();
            List<Prop> propsToSet = propUpdate.getPropsToSet();

            for (Prop prop : propsToSet) {
                if (null == prop) {
                    throw PersoniumCoreException.Dav.XML_CONTENT_ERROR;
                }
                List<Element> lpe = prop.getAny();
                for (Element elem : lpe) {
                    res.setProperty(elem, HttpStatus.SC_OK);
                    String key = elem.getLocalName() + "@" + elem.getNamespaceURI();
                    String value = PersoniumCoreUtils.nodeToString(elem);
                    log.debug("key: " + key);
                    log.debug("val: " + value);
                    propsJson.put(key, value);
                }
            }

            List<Prop> propsToRemove = propUpdate.getPropsToRemove();
            for (Prop prop : propsToRemove) {
                if (null == prop) {
                    throw PersoniumCoreException.Dav.XML_CONTENT_ERROR;
                }
                List<Element> lpe = prop.getAny();
                for (Element elem : lpe) {

                    String key = elem.getLocalName() + "@" + elem.getNamespaceURI();
                    String v = (String) propsJson.get(key);
                    log.debug("Removing key: " + key);
                    if (v == null) {
                        res.setProperty(elem, HttpStatus.SC_NOT_FOUND);
                    } else {
                        propsJson.remove(key);
                        res.setProperty(elem, HttpStatus.SC_OK);
                    }
                }
            }
            // set the last updated date
            this.metaFile.setProperties((JSONObject) propsJson);
            this.metaFile.setUpdated(now);
            this.metaFile.save();
        } finally {
            lock.release();
        }
        ms.getResponse().add(res);
        return ms;
    }

    @Override
    public final ResponseBuilder acl(final Reader reader) {
        // ???????????
        Acl aclToSet = null;
        try {
            aclToSet = ObjectIo.unmarshal(reader, Acl.class);
        } catch (Exception e1) {
            throw PersoniumCoreException.Dav.XML_CONTENT_ERROR.reason(e1);
        }
        if (!aclToSet.validateAcl(isCellLevel())) {
            throw PersoniumCoreException.Dav.XML_VALIDATE_ERROR;
        }
        // 
        Lock lock = this.lock();
        try {
            // ?
            this.load();
            if (!this.exists()) {
                throw getNotFoundException().params(this.getUrl());
            }

            // ACL?xml:base???
            String aclBase = aclToSet.getBase();

            // principal?href ? ??Name  ID__id ???
            List<Ace> aceList = aclToSet.getAceList();
            if (aceList != null) {
                for (Ace ace : aceList) {
                    String pHref = ace.getPrincipalHref();
                    if (pHref != null) {
                        String id = this.cell.roleResourceUrlToId(pHref, aclBase);
                        ace.setPrincipalHref(id);
                    }
                }
            }

            JSONParser parser = new JSONParser();
            JSONObject aclJson = null;
            try {
                aclJson = (JSONObject) parser.parse(aclToSet.toJSON());
            } catch (ParseException e) {
                throw PersoniumCoreException.Dav.XML_ERROR.reason(e);
            }
            // ES?xm:base???? TODO ??????
            aclJson.remove(KEY_ACL_BASE);
            this.metaFile.setAcl(aclJson);
            this.metaFile.save();
            // ?
            return javax.ws.rs.core.Response.status(HttpStatus.SC_OK).header(HttpHeaders.ETAG, this.getEtag());
        } finally {
            lock.release();
        }
    }

    @Override
    public final ResponseBuilder putForCreate(final String contentType, final InputStream inputStream) {
        // Locking
        Lock lock = this.lock();
        try {
            // ???????DavNode???????DavNode????
            // DavNode???????????????404?
            // DavNode??????DavNode??????????????
            this.parent.load();
            if (!this.parent.exists()) {
                throw PersoniumCoreException.Dav.HAS_NOT_PARENT.params(this.parent.getUrl());
            }

            // ??DavNode?????
            if (this.exists()) {
                return this.doPutForUpdate(contentType, inputStream, null);
            }
            // ??DavNode?????????
            return this.doPutForCreate(contentType, inputStream);
        } finally {
            // UNLOCK
            lock.release();
            log.debug("unlock1");
        }
    }

    @Override
    public final ResponseBuilder putForUpdate(final String contentType, final InputStream inputStream,
            String etag) {
        // 
        Lock lock = this.lock();
        try {
            // ????DavNode??????DavNode????
            // ?DavNode?????
            // ?DavNode????????????404?
            // ?DavNode???????????????
            // ?DavNode????
            this.load();
            if (this.metaFile == null) {
                this.parent.load();
                if (this.parent.metaFile == null) {
                    throw getNotFoundException().params(this.parent.getUrl());
                }
                return this.doPutForCreate(contentType, inputStream);
            }
            return this.doPutForUpdate(contentType, inputStream, etag);
        } finally {
            // ?
            lock.release();
            log.debug("unlock2");
        }
    }

    /**
     * Newly create the resource.
     * @param contentType ContentType of the generated file
     * @param inputStream Stream of generated file
     * @return ResponseBuilder
     */
    private ResponseBuilder doPutForCreate(final String contentType, final InputStream inputStream) {
        // check the resource count
        checkChildResourceCount();

        InputStream input = inputStream;
        if (PersoniumUnitConfig.isDavEncryptEnabled()) {
            // Perform encryption.
            DataCryptor cryptor = new DataCryptor(getCellId());
            input = cryptor.encode(inputStream);
        }

        BufferedInputStream bufferedInput = new BufferedInputStream(input);
        try {
            // create new directory.
            Files.createDirectories(Paths.get(this.fsPath));
            // store the file content.
            File newFile = new File(getContentFilePath());
            Files.copy(bufferedInput, newFile.toPath());
            long writtenBytes = newFile.length();
            String encryptionType = DataCryptor.ENCRYPTION_TYPE_NONE;
            if (PersoniumUnitConfig.isDavEncryptEnabled()) {
                writtenBytes = ((CipherInputStream) input).getReadLengthBeforEncryption();
                encryptionType = DataCryptor.ENCRYPTION_TYPE_AES;
            }
            // create new metadata file.
            this.metaFile = DavMetadataFile.prepareNewFile(this, DavCmp.TYPE_DAV_FILE);
            this.metaFile.setContentType(contentType);
            this.metaFile.setContentLength(writtenBytes);
            this.metaFile.setEncryptionType(encryptionType);
            this.metaFile.save();
        } catch (IOException ex) {
            throw PersoniumCoreException.Dav.FS_INCONSISTENCY_FOUND.reason(ex);
        }
        this.isPhantom = false;
        return javax.ws.rs.core.Response.ok().status(HttpStatus.SC_CREATED).header(HttpHeaders.ETAG, getEtag());
    }

    /**
     * Overwrite resources..
     * @param contentType ContentType of the update file
     * @param inputStream Stream of update file
     * @param etag Etag
     * @return ResponseBuilder
     */
    private ResponseBuilder doPutForUpdate(final String contentType, final InputStream inputStream, String etag) {
        // ??
        long now = new Date().getTime();
        // 
        // TODO ??????????????
        this.load();

        // ?(???)?WebDav???????
        // WebDav???????????404??
        if (!this.exists()) {
            throw getNotFoundException().params(getUrl());
        }

        // etag????????*??????????????
        if (etag != null && !"*".equals(etag) && !matchesETag(etag)) {
            throw PersoniumCoreException.Dav.ETAG_NOT_MATCH;
        }

        try {
            // Update Content
            InputStream input = inputStream;
            if (PersoniumUnitConfig.isDavEncryptEnabled()) {
                // Perform encryption.
                DataCryptor cryptor = new DataCryptor(getCellId());
                input = cryptor.encode(inputStream);
            }
            BufferedInputStream bufferedInput = new BufferedInputStream(input);
            File tmpFile = new File(getTempContentFilePath());
            File contentFile = new File(getContentFilePath());
            Files.copy(bufferedInput, tmpFile.toPath());
            Files.delete(contentFile.toPath());
            Files.move(tmpFile.toPath(), contentFile.toPath());

            long writtenBytes = contentFile.length();
            String encryptionType = DataCryptor.ENCRYPTION_TYPE_NONE;
            if (PersoniumUnitConfig.isDavEncryptEnabled()) {
                writtenBytes = ((CipherInputStream) input).getReadLengthBeforEncryption();
                encryptionType = DataCryptor.ENCRYPTION_TYPE_AES;
            }

            // Update Metadata
            this.metaFile.setUpdated(now);
            this.metaFile.setContentType(contentType);
            this.metaFile.setContentLength(writtenBytes);
            this.metaFile.setEncryptionType(encryptionType);
            this.metaFile.save();
        } catch (IOException ex) {
            throw PersoniumCoreException.Dav.FS_INCONSISTENCY_FOUND.reason(ex);
        }

        // response
        return javax.ws.rs.core.Response.ok().status(HttpStatus.SC_NO_CONTENT).header(HttpHeaders.ETAG, getEtag());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final ResponseBuilder get(final String rangeHeaderField) {

        String contentType = getContentType();

        ResponseBuilder res = null;
        String fileFullPath = this.fsPath + File.separator + CONTENT_FILE_NAME;
        long fileSize = getContentLength();
        String encryptionType = getEncryptionType();

        // Range??
        final RangeHeaderHandler range = RangeHeaderHandler.parse(rangeHeaderField, fileSize);

        try {

            // Range??????
            if (!range.isValid()) {
                // ?
                StreamingOutput sout = new StreamingOutputForDavFile(fileFullPath, getCellId(), encryptionType);
                res = davFileResponse(sout, fileSize, contentType);
            } else {
                // Range?

                // Range??
                if (!range.isSatisfiable()) {
                    PersoniumCoreLog.Dav.REQUESTED_RANGE_NOT_SATISFIABLE.params(range.getRangeHeaderField())
                            .writeLog();
                    throw PersoniumCoreException.Dav.REQUESTED_RANGE_NOT_SATISFIABLE;
                }

                if (range.getByteRangeSpecCount() > 1) {
                    // MultiPart???
                    throw PersoniumCoreException.Misc.NOT_IMPLEMENTED.params("Range-MultiPart");
                } else {
                    StreamingOutput sout = new StreamingOutputForDavFileWithRange(fileFullPath, fileSize, range,
                            getCellId(), encryptionType);
                    res = davFileResponseForRange(sout, contentType, range);
                }
            }
            return res.header(HttpHeaders.ETAG, getEtag()).header(PersoniumCoreUtils.HttpHeaders.ACCEPT_RANGES,
                    RangeHeaderHandler.BYTES_UNIT);

        } catch (BinaryDataNotFoundException nex) {
            this.load();
            if (!exists()) {
                throw getNotFoundException().params(getUrl());
            }
            throw PersoniumCoreException.Dav.DAV_UNAVAILABLE.reason(nex);
        }
    }

    /**
     * ??.
     * @param sout
     *            StreamingOuput
     * @param fileSize
     *            
     * @param contentType
     *            
     * @return ?
     */
    public ResponseBuilder davFileResponse(final StreamingOutput sout, long fileSize, String contentType) {
        return javax.ws.rs.core.Response.ok(sout).header(HttpHeaders.CONTENT_LENGTH, fileSize)
                .header(HttpHeaders.CONTENT_TYPE, contentType);
    }

    /**
     * ??.
     * @param sout
     *            StreamingOuput
     * @param contentType
     *            
     * @param range
     *            RangeHeaderHandler
     * @return ?
     */
    private ResponseBuilder davFileResponseForRange(final StreamingOutput sout, String contentType,
            final RangeHeaderHandler range) {
        // MultiPart???????1?byte-renge-set????
        int rangeIndex = 0;
        List<ByteRangeSpec> brss = range.getByteRangeSpecList();
        final ByteRangeSpec brs = brss.get(rangeIndex);

        // iPad?safari????Chunked?Range????????????Content-Length????
        return javax.ws.rs.core.Response.status(HttpStatus.SC_PARTIAL_CONTENT).entity(sout)
                .header(PersoniumCoreUtils.HttpHeaders.CONTENT_RANGE, brs.makeContentRangeHeaderField())
                .header(HttpHeaders.CONTENT_LENGTH, brs.getContentLength())
                .header(HttpHeaders.CONTENT_TYPE, contentType);
    }

    @Override
    public final String getName() {
        return this.name;
    }

    @Override
    public final DavCmp getChild(final String childName) {
        // if self is phantom then all children should be phantom.
        if (this.isPhantom) {
            return DavCmpFsImpl.createPhantom(childName, this);
        }
        // otherwise, child might / might not be phantom.
        return DavCmpFsImpl.create(childName, this);
    }

    @Override
    public String getType() {
        if (this.isPhantom) {
            return DavCmp.TYPE_NULL;
        }
        if (this.metaFile == null) {
            return DavCmp.TYPE_NULL;
        }
        return (String) this.metaFile.getNodeType();
    }

    @Override
    public final ResponseBuilder mkcol(final String type) {
        if (!this.isPhantom) {
            throw new RuntimeException("Bug do not call this .");
        }

        // 
        Lock lock = this.lock();
        try {
            // ????????
            // TODO ?????
            this.parent.load();
            if (!this.parent.exists()) {
                // ?????????
                // ??????409??
                throw PersoniumCoreException.Dav.HAS_NOT_PARENT.params(this.parent.getUrl());
            }
            if (this.exists()) {
                // ????????
                // ??????EXCEPTION
                throw PersoniumCoreException.Dav.METHOD_NOT_ALLOWED;
            }

            // ???
            DavCmpFsImpl current = this;
            int depth = 0;
            int maxDepth = PersoniumUnitConfig.getMaxCollectionDepth();
            while (null != current.parent) {
                current = current.parent;
                depth++;
            }
            if (depth > maxDepth) {
                // ???????400??
                throw PersoniumCoreException.Dav.COLLECTION_DEPTH_ERROR;
            }

            // ???
            checkChildResourceCount();

            // Create New Directory
            Files.createDirectory(this.fsDir.toPath());
            // Create New Meta File
            this.metaFile = DavMetadataFile.prepareNewFile(this, type);
            this.metaFile.save();

            // TODO ?????????

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // UNLOCK
            lock.release();
            log.debug("unlock");
        }
        this.isPhantom = false;

        // Response
        return javax.ws.rs.core.Response.status(HttpStatus.SC_CREATED).header(HttpHeaders.ETAG, this.getEtag());
    }

    /**
     * process MOVE operation.
     * @param etag
     *            ETag Value
     * @param overwrite
     *            whether or not overwrite the target resource
     * @param davDestination
     *            Destination information.
     * @return ResponseBuilder Response Object
     */
    @Override
    public ResponseBuilder move(String etag, String overwrite, DavDestination davDestination) {
        ResponseBuilder res = null;

        // 
        Lock lock = this.lock();
        try {
            // ??
            this.load();
            if (!this.exists()) {
                // ?(??)????
                // ??????404??
                throw getNotFoundException().params(this.getUrl());
            }
            // etag????????*??????????????
            if (etag != null && !"*".equals(etag) && !matchesETag(etag)) {
                throw PersoniumCoreException.Dav.ETAG_NOT_MATCH;
            }

            // ?DavNode?????DavNode??????????????
            // ???DavNode???????????
            // this.parent.nodeId = this.metaFile.getParentId();
            // this.parent.load();
            // if (this.parent.metaFile == null) {
            // throw getNotFoundException().params(this.parent.getUrl());
            // }

            // ?
            davDestination.loadDestinationHierarchy();
            // ??
            davDestination.validateDestinationResource(overwrite, this);

            // MOVE????Box?????????????
            // ??????Object?????
            // ?????????
            AccessContext ac = davDestination.getDestinationRsCmp().getAccessContext();
            // ??
            // ?????????????
            // 1.??ES???????????????????
            // 2.?????????????ES??????
            davDestination.getDestinationRsCmp().getParent().checkAccessContext(ac, BoxPrivilege.WRITE);

            File destDir = ((DavCmpFsImpl) davDestination.getDestinationCmp()).fsDir;
            if (!davDestination.getDestinationCmp().exists()) {
                Files.move(this.fsDir.toPath(), destDir.toPath());
                res = javax.ws.rs.core.Response.status(HttpStatus.SC_CREATED);
            } else {
                FileUtils.deleteDirectory(destDir);
                Files.move(this.fsDir.toPath(), destDir.toPath(), StandardCopyOption.REPLACE_EXISTING);
                res = javax.ws.rs.core.Response.status(HttpStatus.SC_NO_CONTENT);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // UNLOCK
            lock.release();
            log.debug("unlock");
        }

        res.header(HttpHeaders.LOCATION, davDestination.getDestinationUri());
        res.header(HttpHeaders.ETAG, this.getEtag());
        return res;
    }

    private void checkChildResourceCount() {
        // ???
        int maxChildResource = PersoniumUnitConfig.getMaxChildResourceCount();
        if (this.parent.getChildrenCount() >= maxChildResource) {
            // ???????????400??
            throw PersoniumCoreException.Dav.COLLECTION_CHILDRESOURCE_ERROR;
        }
    }

    @Override
    public final ResponseBuilder linkChild(final String childName, final String childNodeId, final Long asof) {
        return null;
    }

    @Override
    public final ResponseBuilder unlinkChild(final String childName, final Long asof) {
        return null;
    }

    /**
     * delete this resource.
     * @param ifMatch ifMatch header
     * @param recursive bool
     * @return JaxRS
     */
    @Override
    public final ResponseBuilder delete(final String ifMatch, boolean recursive) {
        // etag????????*??????????????
        if (ifMatch != null && !"*".equals(ifMatch) && !matchesETag(ifMatch)) {
            throw PersoniumCoreException.Dav.ETAG_NOT_MATCH;
        }
        // 
        Lock lock = this.lock();
        try {
            // 
            this.load();
            if (this.metaFile == null) {
                throw getNotFoundException().params(this.getUrl());
            }
            if (!recursive) {
                // WebDAV????????????
                if (TYPE_COL_WEBDAV.equals(this.getType()) && this.getChildrenCount() > 0) {
                    throw PersoniumCoreException.Dav.HAS_CHILDREN;
                }
            } else {
                // TODO impl recursive
                throw PersoniumCoreException.Misc.NOT_IMPLEMENTED;
            }
            this.doDelete();
        } finally {
            // LOCK
            log.debug("unlock");
            lock.release();
        }
        return javax.ws.rs.core.Response.ok().status(HttpStatus.SC_NO_CONTENT);
    }

    private void doDelete() {
        try {
            FileUtils.deleteDirectory(this.fsDir);
        } catch (IOException e) {
            throw PersoniumCoreException.Dav.FS_INCONSISTENCY_FOUND.reason(e);
        }
    }

    /**
     * ???????.
     * @return ?
     */
    protected BinaryDataAccessor getBinaryDataAccessor() {
        String owner = cell.getOwner();
        String unitUserName = null;
        if (owner == null) {
            unitUserName = AccessContext.TYPE_ANONYMOUS;
        } else {
            unitUserName = IndexNameEncoder.encodeEsIndexName(owner);
        }

        return new BinaryDataAccessor(PersoniumUnitConfig.getBlobStoreRoot(), unitUserName,
                PersoniumUnitConfig.getPhysicalDeleteMode(), PersoniumUnitConfig.getFsyncEnabled());
    }

    @Override
    public final DavCmp getParent() {
        return this.parent;
    }

    @Override
    public final PersoniumODataProducer getODataProducer() {
        return ModelFactory.ODataCtl.userData(this.cell, this);
    }

    @Override
    public final PersoniumODataProducer getSchemaODataProducer(Cell cellObject) {
        return ModelFactory.ODataCtl.userSchema(cellObject, this);
    }

    @Override
    public final int getChildrenCount() {
        return this.getChildDir().length;
    }

    @Override
    public Map<String, DavCmp> getChildren() {
        Map<String, DavCmp> ret = new HashMap<>();
        File[] files = this.getChildDir();
        for (File f : files) {
            String childName = f.getName();
            ret.put(childName, this.getChild(childName));
        }
        return ret;
    }

    /*
     * retrieve child resource dir.
     */
    private File[] getChildDir() {
        File[] children = this.fsDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File child) {
                if (child.isDirectory()) {
                    return true;
                }
                return false;
            }

        });
        return children;
    }

    private Acl translateAcl(JSONObject aclObj) {
        // principal?href ? ID__id?URL???
        // base:xml?
        String baseUrlStr = createBaseUrlStr();

        // TODO ??ES?????????????????????
        // ?????????????
        return this.roleIdToName(aclObj, baseUrlStr);
    }

    /**
     * ID?URL?.
     * jsonObj?IDURL???
     * @param jsonObj
     *            ID??JSON
     * @param baseUrlStr
     *            xml:base
     */
    private Acl roleIdToName(Object jsonObj, String baseUrlStr) {
        Acl ret = Acl.fromJson(((JSONObject) jsonObj).toJSONString());
        List<Ace> aceList = ret.getAceList();
        if (aceList == null) {
            return ret;
        }
        // xml:base
        List<Ace> eraseList = new ArrayList<>();
        for (Ace ace : aceList) {
            String pHref = ace.getPrincipalHref();
            if (pHref != null) {
                // ID????????????????
                String roloResourceUrl = this.cell.roleIdToRoleResourceUrl(pHref);
                log.debug("###" + pHref + ":" + roloResourceUrl);
                if (roloResourceUrl == null) {
                    eraseList.add(ace);
                    continue;
                }
                // base:xml?URL?
                roloResourceUrl = baseUrlToRoleResourceUrl(baseUrlStr, roloResourceUrl);
                ace.setPrincipalHref(roloResourceUrl);
            }
        }
        aceList.removeAll(eraseList);
        ret.setBase(baseUrlStr);
        return ret;
    }

    /**
     * PROPFIND?ACL?xml:base????.
     * @return
     */
    private String createBaseUrlStr() {
        String result = null;
        if (this.box != null) {
            // Box?ACL???Box?URL
            // URL?????????URL??????
            result = String.format(Role.ROLE_RESOURCE_FORMAT, this.cell.getUrl().replaceFirst("/$", ""),
                    this.box.getName(), "");
        } else {
            // Cell?ACL???Box?URL
            // URL?????????URL??????
            result = String.format(Role.ROLE_RESOURCE_FORMAT, this.cell.getUrl().replaceFirst("/$", ""),
                    Box.DEFAULT_BOX_NAME, "");
        }
        return result;
    }

    /**
     * xml:base???RoleResorceUrl?.
     * @param baseUrlStr
     *            xml:base?
     * @param roloResourceUrl
     *            URL
     * @return
     */
    private String baseUrlToRoleResourceUrl(String baseUrlStr, String roloResourceUrlStr) {
        String result = null;
        Role baseUrl = null;
        Role roloResourceUrl = null;
        try {
            // base:xml?URL????????__?
            baseUrl = new Role(new URL(baseUrlStr + "__"));
            roloResourceUrl = new Role(new URL(roloResourceUrlStr));
        } catch (MalformedURLException e) {
            throw PersoniumCoreException.Dav.ROLE_NOT_FOUND.reason(e);
        }
        if (baseUrl.getBoxName().equals(roloResourceUrl.getBoxName())) {
            // base:xml?BOX?URL?BOX????
            result = roloResourceUrl.getName();
        } else {
            // base:xml?BOX?URL?BOX???
            result = String.format(ACL_RELATIVE_PATH_FORMAT, roloResourceUrl.getBoxName(),
                    roloResourceUrl.getName());
        }
        return result;
    }

    /**
     * It judges whether the given string matches the stored Etag.<br>
     * Do not distinguish between Etag and WEtag (Issue #5).
     * @param etag string
     * @return true if given string matches  the stored Etag
     */
    private boolean matchesETag(String etag) {
        if (etag == null) {
            return false;
        }
        String storedEtag = this.getEtag();
        String weakEtag = "W/" + storedEtag;
        return etag.equals(storedEtag) || etag.equals(weakEtag);
    }

    static final String KEY_SCHEMA = "Schema";
    static final String KEY_ACL_BASE = "@base";
    static final String ACL_RELATIVE_PATH_FORMAT = "../%s/%s";

    /**
     * @return cell id
     */
    public String getCellId() {
        return this.cell.getId();
    }

    /**
     * @return DavMetadataFile
     */
    public DavMetadataFile getDavMetadataFile() {
        return this.metaFile;
    }

    /**
     * @return FsPath
     */
    public String getFsPath() {
        return this.fsPath;
    }

    private String getContentFilePath() {
        return this.fsPath + File.separator + CONTENT_FILE_NAME;
    }

    private String getTempContentFilePath() {
        return this.fsPath + File.separator + TEMP_FILE_NAME;
    }

    /**
     * @return URL string of this Dav node.
     */
    public String getUrl() {
        // go to the top ancestor DavCmp (BoxCmp) recursively, and BoxCmp
        // overrides here and give root url.
        return this.parent.getUrl() + "/" + this.name;
    }

    /**
     * BoxId?Es?.
     * @param cellObj Cell
     * @param boxId Id
     * @return ?
     */
    public static Map<String, Object> searchBox(final Cell cellObj, final String boxId) {

        EntitySetAccessor boxType = EsModel.box(cellObj);
        PersoniumGetResponse getRes = boxType.get(boxId);
        if (getRes == null || !getRes.isExists()) {
            PersoniumCoreLog.Dav.ROLE_NOT_FOUND.params("Box Id Not Hit").writeLog();

            throw PersoniumCoreException.Dav.ROLE_NOT_FOUND;
        }
        return getRes.getSource();
    }

    /**
     * retruns NotFoundException for this resource. <br />
     * messages should vary among resource type Cell, box, file, etc..
     * Each *Cmp class should override this method and define the proper exception <br />
     * Additional info (reason etc.) for the message should be set after calling this method.
     * @return NotFoundException
     */
    public PersoniumCoreException getNotFoundException() {
        return PersoniumCoreException.Dav.RESOURCE_NOT_FOUND;
    }

    @Override
    public Cell getCell() {
        return this.cell;
    }

    @Override
    public Box getBox() {
        return this.box;
    }

    @Override
    public Long getUpdated() {
        return this.metaFile.getUpdated();
    }

    @Override
    public Long getPublished() {
        return this.metaFile.getPublished();
    }

    @Override
    public Long getContentLength() {
        return this.metaFile.getContentLength();
    }

    @Override
    public String getContentType() {
        return this.metaFile.getContentType();
    }

    @Override
    public String getEncryptionType() {
        return this.metaFile.getEncryptionType();
    }

    @Override
    public String getId() {
        return this.metaFile.getNodeId();
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, String> getProperties() {
        return this.metaFile.getProperties();
    }

}