Java tutorial
package org.apache.archiva.webdav; /* * 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. */ import org.apache.archiva.admin.model.beans.ManagedRepository; import org.apache.archiva.metadata.model.facets.AuditEvent; import org.apache.archiva.repository.events.AuditListener; import org.apache.archiva.common.filelock.FileLockException; import org.apache.archiva.common.filelock.FileLockManager; import org.apache.archiva.common.filelock.FileLockTimeoutException; import org.apache.archiva.common.filelock.Lock; import org.apache.archiva.redback.components.taskqueue.TaskQueueException; import org.apache.archiva.scheduler.ArchivaTaskScheduler; import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler; import org.apache.archiva.scheduler.repository.model.RepositoryTask; import org.apache.archiva.webdav.util.IndexWriter; import org.apache.archiva.webdav.util.MimeTypes; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.jackrabbit.util.Text; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceFactory; import org.apache.jackrabbit.webdav.DavResourceIterator; import org.apache.jackrabbit.webdav.DavResourceIteratorImpl; import org.apache.jackrabbit.webdav.DavResourceLocator; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.MultiStatusResponse; import org.apache.jackrabbit.webdav.io.InputContext; import org.apache.jackrabbit.webdav.io.OutputContext; import org.apache.jackrabbit.webdav.lock.ActiveLock; import org.apache.jackrabbit.webdav.lock.LockInfo; import org.apache.jackrabbit.webdav.lock.LockManager; import org.apache.jackrabbit.webdav.lock.Scope; import org.apache.jackrabbit.webdav.lock.Type; import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; import org.apache.jackrabbit.webdav.property.DavPropertySet; import org.apache.jackrabbit.webdav.property.DefaultDavProperty; import org.apache.jackrabbit.webdav.property.ResourceType; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; /** */ public class ArchivaDavResource implements DavResource { public static final String HIDDEN_PATH_PREFIX = "."; private final ArchivaDavResourceLocator locator; private final DavResourceFactory factory; private final File localResource; private final String logicalResource; private DavPropertySet properties = null; private LockManager lockManager; private final DavSession session; private String remoteAddr; private final ManagedRepository repository; private final MimeTypes mimeTypes; private List<AuditListener> auditListeners; private String principal; public static final String COMPLIANCE_CLASS = "1, 2"; private final ArchivaTaskScheduler scheduler; private final FileLockManager fileLockManager; private Logger log = LoggerFactory.getLogger(ArchivaDavResource.class); public ArchivaDavResource(String localResource, String logicalResource, ManagedRepository repository, DavSession session, ArchivaDavResourceLocator locator, DavResourceFactory factory, MimeTypes mimeTypes, List<AuditListener> auditListeners, RepositoryArchivaTaskScheduler scheduler, FileLockManager fileLockManager) { this.localResource = new File(localResource); this.logicalResource = logicalResource; this.locator = locator; this.factory = factory; this.session = session; // TODO: push into locator as well as moving any references out of the resource factory this.repository = repository; // TODO: these should be pushed into the repository layer, along with the physical file operations in this class this.mimeTypes = mimeTypes; this.auditListeners = auditListeners; this.scheduler = scheduler; this.fileLockManager = fileLockManager; } public ArchivaDavResource(String localResource, String logicalResource, ManagedRepository repository, String remoteAddr, String principal, DavSession session, ArchivaDavResourceLocator locator, DavResourceFactory factory, MimeTypes mimeTypes, List<AuditListener> auditListeners, RepositoryArchivaTaskScheduler scheduler, FileLockManager fileLockManager) { this(localResource, logicalResource, repository, session, locator, factory, mimeTypes, auditListeners, scheduler, fileLockManager); this.remoteAddr = remoteAddr; this.principal = principal; } @Override public String getComplianceClass() { return COMPLIANCE_CLASS; } @Override public String getSupportedMethods() { return METHODS; } @Override public boolean exists() { return localResource.exists(); } @Override public boolean isCollection() { return localResource.isDirectory(); } @Override public String getDisplayName() { String resPath = getResourcePath(); return (resPath != null) ? Text.getName(resPath) : resPath; } @Override public DavResourceLocator getLocator() { return locator; } public File getLocalResource() { return localResource; } @Override public String getResourcePath() { return locator.getResourcePath(); } @Override public String getHref() { return locator.getHref(isCollection()); } @Override public long getModificationTime() { return localResource.lastModified(); } @Override public void spool(OutputContext outputContext) throws IOException { if (!isCollection()) { outputContext.setContentLength(localResource.length()); outputContext.setContentType(mimeTypes.getMimeType(localResource.getName())); } try { if (!isCollection() && outputContext.hasStream()) { Lock lock = fileLockManager.readFileLock(localResource); try (InputStream is = Files.newInputStream(lock.getFile().toPath())) { IOUtils.copy(is, outputContext.getOutputStream()); } } else if (outputContext.hasStream()) { IndexWriter writer = new IndexWriter(this, localResource, logicalResource); writer.write(outputContext); } } catch (FileLockException e) { throw new IOException(e.getMessage(), e); } catch (FileLockTimeoutException e) { throw new IOException(e.getMessage(), e); } } @Override public DavPropertyName[] getPropertyNames() { return getProperties().getPropertyNames(); } @Override public DavProperty getProperty(DavPropertyName name) { return getProperties().get(name); } @Override public DavPropertySet getProperties() { return initProperties(); } @Override public void setProperty(DavProperty property) throws DavException { } @Override public void removeProperty(DavPropertyName propertyName) throws DavException { } public MultiStatusResponse alterProperties(DavPropertySet setProperties, DavPropertyNameSet removePropertyNames) throws DavException { return null; } @SuppressWarnings("unchecked") @Override public MultiStatusResponse alterProperties(List changeList) throws DavException { return null; } @Override public DavResource getCollection() { DavResource parent = null; if (getResourcePath() != null && !getResourcePath().equals("/")) { String parentPath = Text.getRelativeParent(getResourcePath(), 1); if (parentPath.equals("")) { parentPath = "/"; } DavResourceLocator parentloc = locator.getFactory().createResourceLocator(locator.getPrefix(), parentPath); try { parent = factory.createResource(parentloc, session); } catch (DavException e) { // should not occur } } return parent; } @Override public void addMember(DavResource resource, InputContext inputContext) throws DavException { File localFile = new File(localResource, resource.getDisplayName()); boolean exists = localFile.exists(); if (isCollection() && inputContext.hasStream()) // New File { try (OutputStream stream = Files.newOutputStream(localFile.toPath())) { IOUtils.copy(inputContext.getInputStream(), stream); } catch (IOException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } // TODO: a bad deployment shouldn't delete an existing file - do we need to write to a temporary location first? long expectedContentLength = inputContext.getContentLength(); long actualContentLength = localFile.length(); // length of -1 is given for a chunked request or unknown length, in which case we accept what was uploaded if (expectedContentLength >= 0 && expectedContentLength != actualContentLength) { String msg = "Content Header length was " + expectedContentLength + " but was " + actualContentLength; log.debug("Upload failed: {}", msg); FileUtils.deleteQuietly(localFile); throw new DavException(HttpServletResponse.SC_BAD_REQUEST, msg); } queueRepositoryTask(localFile); log.debug("File '{}{}(current user '{}')", resource.getDisplayName(), (exists ? "' modified " : "' created "), this.principal); triggerAuditEvent(resource, exists ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE); } else if (!inputContext.hasStream() && isCollection()) // New directory { localFile.mkdir(); log.debug("Directory '{}' (current user '{}')", resource.getDisplayName(), this.principal); triggerAuditEvent(resource, AuditEvent.CREATE_DIR); } else { String msg = "Could not write member " + resource.getResourcePath() + " at " + getResourcePath() + " as this is not a DAV collection"; log.debug(msg); throw new DavException(HttpServletResponse.SC_BAD_REQUEST, msg); } } @Override public DavResourceIterator getMembers() { List<DavResource> list = new ArrayList<>(); if (exists() && isCollection()) { for (String item : localResource.list()) { try { if (!item.startsWith(HIDDEN_PATH_PREFIX)) { String path = locator.getResourcePath() + '/' + item; DavResourceLocator resourceLocator = locator.getFactory() .createResourceLocator(locator.getPrefix(), path); DavResource resource = factory.createResource(resourceLocator, session); if (resource != null) { list.add(resource); } log.debug("Resource '{}' retrieved by '{}'", item, this.principal); } } catch (DavException e) { // Should not occur } } } return new DavResourceIteratorImpl(list); } @Override public void removeMember(DavResource member) throws DavException { File resource = checkDavResourceIsArchivaDavResource(member).getLocalResource(); if (resource.exists()) { try { if (resource.isDirectory()) { if (!FileUtils.deleteQuietly(resource)) { throw new IOException("Could not remove directory"); } triggerAuditEvent(member, AuditEvent.REMOVE_DIR); } else { if (!resource.delete()) { throw new IOException("Could not remove file"); } triggerAuditEvent(member, AuditEvent.REMOVE_FILE); } log.debug("{}{}' removed (current user '{}')", (resource.isDirectory() ? "Directory '" : "File '"), member.getDisplayName(), this.principal); } catch (IOException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } else { throw new DavException(HttpServletResponse.SC_NOT_FOUND); } } private void triggerAuditEvent(DavResource member, String action) throws DavException { String path = logicalResource + "/" + member.getDisplayName(); ArchivaDavResource resource = checkDavResourceIsArchivaDavResource(member); AuditEvent auditEvent = new AuditEvent(locator.getRepositoryId(), resource.principal, path, action); auditEvent.setRemoteIP(resource.remoteAddr); for (AuditListener listener : auditListeners) { listener.auditEvent(auditEvent); } } @Override public void move(DavResource destination) throws DavException { if (!exists()) { throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist."); } try { ArchivaDavResource resource = checkDavResourceIsArchivaDavResource(destination); if (isCollection()) { FileUtils.moveDirectory(getLocalResource(), resource.getLocalResource()); triggerAuditEvent(remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_DIRECTORY); } else { FileUtils.moveFile(getLocalResource(), resource.getLocalResource()); triggerAuditEvent(remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.MOVE_FILE); } log.debug("{}{}' moved to '{}' (current user '{}')", (isCollection() ? "Directory '" : "File '"), getLocalResource().getName(), destination, this.principal); } catch (IOException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } @Override public void copy(DavResource destination, boolean shallow) throws DavException { if (!exists()) { throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Resource to copy does not exist."); } if (shallow && isCollection()) { throw new DavException(DavServletResponse.SC_FORBIDDEN, "Unable to perform shallow copy for collection"); } try { ArchivaDavResource resource = checkDavResourceIsArchivaDavResource(destination); if (isCollection()) { FileUtils.copyDirectory(getLocalResource(), resource.getLocalResource()); triggerAuditEvent(remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_DIRECTORY); } else { FileUtils.copyFile(getLocalResource(), resource.getLocalResource()); triggerAuditEvent(remoteAddr, locator.getRepositoryId(), logicalResource, AuditEvent.COPY_FILE); } log.debug("{}{}' copied to '{}' (current user '{)')", (isCollection() ? "Directory '" : "File '"), getLocalResource().getName(), destination, this.principal); } catch (IOException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } @Override public boolean isLockable(Type type, Scope scope) { return Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope); } @Override public boolean hasLock(Type type, Scope scope) { return getLock(type, scope) != null; } @Override public ActiveLock getLock(Type type, Scope scope) { ActiveLock lock = null; if (exists() && Type.WRITE.equals(type) && Scope.EXCLUSIVE.equals(scope)) { lock = lockManager.getLock(type, scope, this); } return lock; } @Override public ActiveLock[] getLocks() { ActiveLock writeLock = getLock(Type.WRITE, Scope.EXCLUSIVE); return (writeLock != null) ? new ActiveLock[] { writeLock } : new ActiveLock[0]; } @Override public ActiveLock lock(LockInfo lockInfo) throws DavException { ActiveLock lock = null; if (isLockable(lockInfo.getType(), lockInfo.getScope())) { lock = lockManager.createLock(lockInfo, this); } else { throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "Unsupported lock type or scope."); } return lock; } @Override public ActiveLock refreshLock(LockInfo lockInfo, String lockToken) throws DavException { if (!exists()) { throw new DavException(DavServletResponse.SC_NOT_FOUND); } ActiveLock lock = getLock(lockInfo.getType(), lockInfo.getScope()); if (lock == null) { throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "No lock with the given type/scope present on resource " + getResourcePath()); } lock = lockManager.refreshLock(lockInfo, lockToken, this); return lock; } @Override public void unlock(String lockToken) throws DavException { ActiveLock lock = getLock(Type.WRITE, Scope.EXCLUSIVE); if (lock == null) { throw new DavException(HttpServletResponse.SC_PRECONDITION_FAILED); } else if (lock.isLockedByToken(lockToken)) { lockManager.releaseLock(lockToken, this); } else { throw new DavException(DavServletResponse.SC_LOCKED); } } @Override public void addLockManager(LockManager lockManager) { this.lockManager = lockManager; } @Override public DavResourceFactory getFactory() { return factory; } @Override public DavSession getSession() { return session; } /** * Fill the set of properties */ protected DavPropertySet initProperties() { if (!exists()) { properties = new DavPropertySet(); } if (properties != null) { return properties; } DavPropertySet properties = new DavPropertySet(); // set (or reset) fundamental properties if (getDisplayName() != null) { properties.add(new DefaultDavProperty(DavPropertyName.DISPLAYNAME, getDisplayName())); } if (isCollection()) { properties.add(new ResourceType(ResourceType.COLLECTION)); // Windows XP support properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, "1")); } else { properties.add(new ResourceType(ResourceType.DEFAULT_RESOURCE)); // Windows XP support properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, "0")); } // Need to get the ISO8601 date for properties DateTime dt = new DateTime(localResource.lastModified()); DateTimeFormatter fmt = ISODateTimeFormat.dateTime(); String modifiedDate = fmt.print(dt); properties.add(new DefaultDavProperty(DavPropertyName.GETLASTMODIFIED, modifiedDate)); properties.add(new DefaultDavProperty(DavPropertyName.CREATIONDATE, modifiedDate)); properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTLENGTH, localResource.length())); this.properties = properties; return properties; } private ArchivaDavResource checkDavResourceIsArchivaDavResource(DavResource resource) throws DavException { if (!(resource instanceof ArchivaDavResource)) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "DavResource is not instance of ArchivaDavResource"); } return (ArchivaDavResource) resource; } private void triggerAuditEvent(String remoteIP, String repositoryId, String resource, String action) { AuditEvent event = new AuditEvent(repositoryId, principal, resource, action); event.setRemoteIP(remoteIP); for (AuditListener listener : auditListeners) { listener.auditEvent(event); } } private void queueRepositoryTask(File localFile) { RepositoryTask task = new RepositoryTask(); task.setRepositoryId(repository.getId()); task.setResourceFile(localFile); task.setUpdateRelatedArtifacts(false); task.setScanAll(false); try { scheduler.queueTask(task); } catch (TaskQueueException e) { log.error("Unable to queue repository task to execute consumers on resource file ['" + localFile.getName() + "']."); } } }