Java tutorial
/** * 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(); } }