INT-4117: File Outbound Set File Permissions
JIRA: https://jira.spring.io/browse/INT-4117 Add an option to set file permissions on the `FileWritingMessageHandler` when the file system supports it. Polishing - PR Comments; more robust file system detection. Add DSL support and fix NPE with missing timestamp header. Fix javadoc Fix javadoc Some polishing: * Add `chmod >= 0` assertion * Make some assertion with string concatenation as `Supplier`-based to deffer that string concatenation
This commit is contained in:
committed by
Artem Bilan
parent
c8aafde20b
commit
2b99c191f6
@@ -29,10 +29,14 @@ import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.BitSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -48,6 +52,7 @@ import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.expression.ExpressionUtils;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.file.support.FileUtils;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.MessageTriggerAction;
|
||||
import org.springframework.integration.support.locks.DefaultLockRegistry;
|
||||
@@ -152,6 +157,8 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
|
||||
private volatile boolean preserveTimestamp;
|
||||
|
||||
private Set<PosixFilePermission> permissions;
|
||||
|
||||
/**
|
||||
* Constructor which sets the {@link #destinationDirectoryExpression} using
|
||||
* a {@link LiteralExpression}.
|
||||
@@ -367,6 +374,75 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
this.preserveTimestamp = preserveTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* String setter for Spring XML convenience.
|
||||
* @param chmod permissions as an octal string e.g "600";
|
||||
* @see #setChmod(int)
|
||||
* @since 5.0
|
||||
*/
|
||||
public void setChmodOctal(String chmod) {
|
||||
Assert.notNull(chmod, "'chmod' cannot be null");
|
||||
setChmod(Integer.parseInt(chmod, 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file permissions after uploading, e.g. 0600 for
|
||||
* owner read/write. Only applies to file systems that support posix
|
||||
* file permissions.
|
||||
* @param chmod the permissions.
|
||||
* @throws IllegalArgumentException if the value is higher than 0777.
|
||||
* @since 5.0
|
||||
*/
|
||||
public void setChmod(int chmod) {
|
||||
Assert.isTrue(chmod >= 0 && chmod <= 0777, "'chmod' must be between 0 and 0777 (octal)");
|
||||
if (!FileUtils.IS_POSIX) {
|
||||
this.logger.error("'chmod' setting ignored - the file system does not support Posix attributes");
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Bitset.valueOf(byte[]) takes a little-endian array of bytes to create a BitSet.
|
||||
* Since we are interested in 9 bits, we construct an array with the low-order byte
|
||||
* (bits 0-7) followed by the second order byte (bit 8).
|
||||
* BitSet.stream() returns a stream of ints representing those bits that are set.
|
||||
* We use that stream with a switch to create the set of PosixFilePermissions
|
||||
* representing the bits that were set in the chmod value.
|
||||
*/
|
||||
BitSet bits = BitSet.valueOf(new byte[] { (byte) chmod, (byte) (chmod >> 8) });
|
||||
final Set<PosixFilePermission> permissions = new HashSet<>();
|
||||
bits.stream().forEach(b -> {
|
||||
switch (b) {
|
||||
case 0:
|
||||
permissions.add(PosixFilePermission.OTHERS_EXECUTE);
|
||||
break;
|
||||
case 1:
|
||||
permissions.add(PosixFilePermission.OTHERS_WRITE);
|
||||
break;
|
||||
case 2:
|
||||
permissions.add(PosixFilePermission.OTHERS_READ);
|
||||
break;
|
||||
case 3:
|
||||
permissions.add(PosixFilePermission.GROUP_EXECUTE);
|
||||
break;
|
||||
case 4:
|
||||
permissions.add(PosixFilePermission.GROUP_WRITE);
|
||||
break;
|
||||
case 5:
|
||||
permissions.add(PosixFilePermission.GROUP_READ);
|
||||
break;
|
||||
case 6:
|
||||
permissions.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
break;
|
||||
case 7:
|
||||
permissions.add(PosixFilePermission.OWNER_WRITE);
|
||||
break;
|
||||
case 8:
|
||||
permissions.add(PosixFilePermission.OWNER_READ);
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
||||
@@ -414,14 +490,14 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
private void validateDestinationDirectory(File destinationDirectory, boolean autoCreateDirectory) {
|
||||
if (!destinationDirectory.exists() && autoCreateDirectory) {
|
||||
Assert.isTrue(destinationDirectory.mkdirs(),
|
||||
"Destination directory [" + destinationDirectory + "] could not be created.");
|
||||
() -> "Destination directory [" + destinationDirectory + "] could not be created.");
|
||||
}
|
||||
Assert.isTrue(destinationDirectory.exists(),
|
||||
"Destination directory [" + destinationDirectory + "] does not exist.");
|
||||
() -> "Destination directory [" + destinationDirectory + "] does not exist.");
|
||||
Assert.isTrue(destinationDirectory.isDirectory(),
|
||||
"Destination path [" + destinationDirectory + "] does not point to a directory.");
|
||||
() -> "Destination path [" + destinationDirectory + "] does not point to a directory.");
|
||||
Assert.isTrue(Files.isWritable(destinationDirectory.toPath()),
|
||||
"Destination directory [" + destinationDirectory + "] is not writable.");
|
||||
() -> "Destination directory [" + destinationDirectory + "] is not writable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -485,7 +561,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
else {
|
||||
if (this.logger.isWarnEnabled()) {
|
||||
this.logger.warn("Could not set lastModified, header " + FileHeaders.SET_MODIFIED
|
||||
+ " must be a Number, not " + timestamp.getClass());
|
||||
+ " must be a Number, not " + (timestamp == null ? "null" : timestamp.getClass()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,7 +806,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
fileToWriteTo = tempFile;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported FileExistsMode " + this.fileExistsMode);
|
||||
throw new IllegalStateException("Unsupported FileExistsMode: " + this.fileExistsMode);
|
||||
}
|
||||
return fileToWriteTo;
|
||||
}
|
||||
@@ -745,6 +821,20 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
|
||||
if (this.deleteSourceFiles && originalFile != null) {
|
||||
originalFile.delete();
|
||||
}
|
||||
|
||||
setPermissions(resultFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set permissions on newly written files.
|
||||
* @param resultFile the file.
|
||||
* @throws IOException any exception.
|
||||
* @since 5.0
|
||||
*/
|
||||
protected void setPermissions(File resultFile) throws IOException {
|
||||
if (this.permissions != null) {
|
||||
Files.setPosixFilePermissions(resultFile.toPath(), this.permissions);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameTo(File tempFile, File resultFile) throws IOException {
|
||||
|
||||
@@ -75,6 +75,7 @@ abstract class FileWritingMessageHandlerBeanDefinitionBuilder {
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "flush-interval");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "flush-when-idle");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "flush-predicate");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "chmod");
|
||||
String remoteFileNameGenerator = element.getAttribute("filename-generator");
|
||||
String remoteFileNameGeneratorExpression = element.getAttribute("filename-generator-expression");
|
||||
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
|
||||
|
||||
@@ -73,6 +73,8 @@ public class FileWritingMessageHandlerFactoryBean
|
||||
|
||||
private volatile MessageFlushPredicate flushPredicate;
|
||||
|
||||
private String chmod;
|
||||
|
||||
public void setFileExistsMode(String fileExistsModeAsString) {
|
||||
this.fileExistsMode = FileExistsMode.getForString(fileExistsModeAsString);
|
||||
}
|
||||
@@ -137,6 +139,10 @@ public class FileWritingMessageHandlerFactoryBean
|
||||
this.flushPredicate = flushPredicate;
|
||||
}
|
||||
|
||||
public void setChmod(String chmod) {
|
||||
this.chmod = chmod;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FileWritingMessageHandler createHandler() {
|
||||
|
||||
@@ -195,6 +201,9 @@ public class FileWritingMessageHandlerFactoryBean
|
||||
if (this.flushPredicate != null) {
|
||||
handler.setFlushPredicate(this.flushPredicate);
|
||||
}
|
||||
if (this.chmod != null) {
|
||||
handler.setChmodOctal(this.chmod);
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
|
||||
* The {@link MessageHandlerSpec} for the {@link FileWritingMessageHandler}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@@ -245,6 +246,20 @@ public class FileWritingMessageHandlerSpec
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file permissions after uploading, e.g. 0600 for
|
||||
* owner read/write. Only applies to file systems that support posix
|
||||
* file permissions.
|
||||
* @param chmod the permissions.
|
||||
* @throws IllegalArgumentException if the value is higher than 0777.
|
||||
* @return the spec.
|
||||
* @see FileWritingMessageHandler#setChmod(int)
|
||||
*/
|
||||
public FileWritingMessageHandlerSpec chmod(int chmod) {
|
||||
this.target.setChmod(chmod);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> getComponentsToRegister() {
|
||||
if (this.defaultFileNameGenerator != null) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.integration.file.support;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
|
||||
/**
|
||||
* Utilities for operations on Files.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
public final class FileUtils {
|
||||
|
||||
public static final boolean IS_POSIX = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
|
||||
|
||||
private FileUtils() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -597,6 +597,7 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="chmod" />
|
||||
<xsd:attributeGroup ref="integration:smartLifeCycleAttributeGroup"/>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -1027,4 +1028,15 @@ Only files matching this regular expression will be picked up by this adapter.
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:attributeGroup name="chmod">
|
||||
<xsd:attribute name="chmod" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Change the mode of the file (or remote file) after writing. Integer value
|
||||
expressed in Octal, e.g. '644'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
Reference in New Issue
Block a user