Copy remote file in sftp using Spring Integration - spring-integration

I need to copy/duplicate remote file in Sftp server also rename when copied, I read here that copying remote file in Sftp isn't supported so the only the available option I had is to GET file into Local and then PUT again to Sftp & delete the Local file, I have successfully achieved my goal but the problem is there is a log printing from org.springframework.core.log.LogAccessor: I have no idea from where it is coming.
Code that helps in copying remote file:
#Bean
public IntegrationFlow copyRemoteFile() {
return IntegrationFlows.from("integration.channel.copy")
.handle(Sftp.outboundGateway(sftpSessionFactory(),
AbstractRemoteFileOutboundGateway.Command.GET,
"headers[" + COPY_SOURCE_PATH.value + "]+'/'+" +
"headers[" + COPY_SOURCE_FILENAME.value + "]")
.autoCreateLocalDirectory(true)
.fileExistsMode(FileExistsMode.REPLACE)
.localDirectory(new File(localPath)))
.log(LoggingHandler.Level.INFO, "SftpCopyService")
.handle(Sftp.outboundGateway(sftpSessionFactory(),
AbstractRemoteFileOutboundGateway.Command.PUT,
"payload")
.remoteDirectoryExpression("headers[" + COPY_DEST_PATH.value + "]")
.fileNameGenerator(n -> (String)n.getHeaders().get(COPY_DEST_FILENAME.value))
.fileExistsMode(FileExistsMode.REPLACE))
.log(LoggingHandler.Level.INFO, "SftpCopyService")
.handle((p, h) -> {
try {
return Files.deleteIfExists(
Paths.get(localPath + File.separator + h.get(COPY_SOURCE_FILENAME.value)));
} catch (IOException e) {
e.printStackTrace();
return false;
}
})
.get();
Here is the log.
2021-02-16 18:10:22,577 WARN [http-nio-9090-exec-1] org.springframework.core.log.LogAccessor: Failed to delete C:\Users\DELL\Desktop\GetTest\Spring Integration.txt
2021-02-16 18:10:22,784 INFO [http-nio-9090-exec-1] org.springframework.core.log.LogAccessor: GenericMessage [payload=C:\Users\DELL\Desktop\GetTest\Spring Integration.txt, headers={file_remoteHostPort=X.X.X.X:22, replyChannel=nullChannel, sourceFileName=Spring Integration.txt, file_remoteDirectory=/uploads/, destFileName=Spring Integrat.txt, destPath=uploads/dest, id=5105bdd1-8180-1185-3661-2ed708e07ab9, sourcePath=/uploads, file_remoteFile=Spring Integration.txt, timestamp=1613479222779}]
2021-02-16 18:10:23,011 INFO [http-nio-9090-exec-1] org.springframework.core.log.LogAccessor: GenericMessage [payload=uploads/dest/Spring Integrat.txt, headers={file_remoteHostPort=X.X.X.X:22, replyChannel=nullChannel, sourceFileName=Spring Integration.txt, file_remoteDirectory=/uploads/, destFileName=Spring Integrat.txt, destPath=uploads/dest, id=1bf83b0f-3b24-66bd-ffbf-2a9018b499fb, sourcePath=/uploads, file_remoteFile=Spring Integration.txt, timestamp=1613479223011}]
The more surprising part is, it appears very early even before the flow is executed, though I have handled file deletion at very last. How can i get rid of this log message? though it doesn't effect my process but the log message is misleading
Also is there any better way to copy remote file to another path inside sftp
EDIT
Like you suggested I tried the SftpRemoteFileTemplate.execute() method to copy files in sftp but when the session.write(InputStream stream,String path) method is called the method control never returns it keeps the control forever
I tried debugging, the control is lost when the execution reaches here:
for(_ackcount = this.seq - startid; _ackcount > ackcount && this.checkStatus((int[])null, header); ++ackcount) {
}
This code sits inside _put method of ChannelSftp.class
Here is the sample code that I'm trying
public boolean copy() {
return remoteFileTemplate.execute(session -> {
if (!session.exists("uploads/Spring Integration.txt")){
return false;
}
if (!session.exists("uploads/dest")){
session.mkdir("uploads/dest");
}
InputStream inputStream = session.readRaw("uploads/Spring Integration.txt");
session.write(inputStream, "uploads/dest/spring.txt");
session.finalizeRaw();
return true;
});
}
Would you please point out what mistake I'm doing here?

Instead of writing the whole flow via local file copy, I'd suggest to look into a single service activator for the SftpRemoteFileTemplate.execute(SessionCallback<F, T>). The provided SftpSession in that callback can be used for the InputStream readRaw() and write(InputStream inputStream, String destination). In the end you must call finalizeRaw().
The LogAccessor issue is not clear. What Spring Integration version do you use? Do you override Spring Core version though?
I think we can improve that WARN message and don't call File.delete() if it does not exists().
Feel free to provide such a contribution!
UPDATE
The JUnit test to demonstrate how to perform a copy on SFTP server:
#Test
public void testSftpCopy() throws Exception {
this.template.execute(session -> {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream(in);
session.read("sftpSource/sftpSource2.txt", out);
session.write(in, "sftpTarget/sftpTarget2.txt");
return null;
});
Session<?> session = this.sessionFactory.getSession();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileCopyUtils.copy(session.readRaw("sftpTarget/sftpTarget2.txt"), baos);
assertThat(session.finalizeRaw()).isTrue();
assertThat(new String(baos.toByteArray())).isEqualTo("source2");
baos = new ByteArrayOutputStream();
FileCopyUtils.copy(session.readRaw("sftpSource/sftpSource2.txt"), baos);
assertThat(session.finalizeRaw()).isTrue();
assertThat(new String(baos.toByteArray())).isEqualTo("source2");
session.close();
}

Related

Azure Durable function removes files form local storage after it is downloaded

I am struggling a lot with this task. I have to download files from SFTP and then parse them. I am using Durable functions like this
[FunctionName("MainOrch")]
public async Task<List<string>> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context, ILogger log)
{
try
{
var filesDownloaded = new List<string>();
var filesUploaded = new List<string>();
var files = await context.CallActivityAsync<List<string>>("SFTPGetListOfFiles", null);
log.LogInformation("!!!!FilesFound*******!!!!!" + files.Count);
if (files.Count > 0)
{
foreach (var fileName in files)
{
filesDownloaded.Add(await context.CallActivityAsync<string>("SFTPDownload", fileName));
}
var parsingTasks = new List<Task<string>>(filesDownloaded.Count);
foreach (var downlaoded in filesDownloaded)
{
var parsingTask = context.CallActivityAsync<string>("PBARParsing", downlaoded);
parsingTasks.Add(parsingTask);
}
await Task.WhenAll(parsingTasks);
}
return filesDownloaded;
}
catch (Exception ex)
{
throw;
}
}
SFTPGetListOfFiles: This functions connects to SFTP and gets the list of files in a folder and return.
SFTPDownload: This function is suppose to connect to SFTP and download each file in Azure Function's Tempt Storage. and return the download path. (each file is from 10 to 60 MB)
[FunctionName("SFTPDownload")]
public async Task<string> SFTPDownload([ActivityTrigger] string name, ILogger log, Microsoft.Azure.WebJobs.ExecutionContext context)
{
var downloadPath = "";
try
{
using (var session = new Session())
{
try
{
session.ExecutablePath = Path.Combine(context.FunctionAppDirectory, "winscp.exe");
session.Open(GetOptions(context));
log.LogInformation("!!!!!!!!!!!!!!Connected For Download!!!!!!!!!!!!!!!");
TransferOptions transferOptions = new TransferOptions();
transferOptions.TransferMode = TransferMode.Binary;
downloadPath = Path.Combine(Path.GetTempPath(), name);
log.LogInformation("Downloading " + name);
var transferResult = session.GetFiles("/Receive/" + name, downloadPath, false, transferOptions);
log.LogInformation("Downloaded " + name);
// Throw on any error
transferResult.Check();
log.LogInformation("!!!!!!!!!!!!!!Completed Download !!!!!!!!!!!!!!!!");
}
catch (Exception ex)
{
log.LogError(ex.Message);
}
finally
{
session.Close();
}
}
}
catch (Exception ex)
{
log.LogError(ex.Message);
_traceService.TraceException(ex);
}
return downloadPath;
}
PBARParsing: function has to get the stream of that file and process it (processing a 60 MB file might take few minutes on Scale up of S2 and Scale out with 10 instances.)
[FunctionName("PBARParsing")]
public async Task PBARParsing([ActivityTrigger] string pathOfFile,
ILogger log)
{
var theSplit = pathOfFile.Split("\\");
var name = theSplit[theSplit.Length - 1];
try
{
log.LogInformation("**********Starting" + name);
Stream stream = File.OpenRead(pathOfFile);
i want the download of all files to be completed using SFTPDownload thats why "await" is in a loop. and then i want parsing to run in parallel.
Question 1: Does the code in MainOrch function seems correct for doing these 3 things 1)getting the names of files, 2) downloading them one by one and not starting the parsing function until all files are downloaded. and then 3)parsing the files in parallel. ?
I observed that what i mentioned in Question 1 is working as expected.
Question 2: 30% of the files are parsed and for the 80% i see errors that "Could not find file 'D:\local\Temp\fileName'" is azure function removing the files after i place them ? is there any other approach i can take? If i change the path to "D:\home" i might see "File is being used by another process" error. but i haven't tried it yet. out the 68 files on SFTP weirdly last 20 ran and first 40 files were not found at that path and this is in sequence.
Question3: I also see this error " Singleton lock renewal failed for blob 'func-eres-integration-dev/host' with error code 409: LeaseIdMismatchWithLeaseOperation. The last successful renewal completed at 2020-08-08T17:57:10.494Z (46005 milliseconds ago) with a duration of 155 milliseconds. The lease period was 15000 milliseconds." does it tells something ? it came just once though.
update
after using "D:\home" i am not getting file not found errors
For others coming across this, the temporary storage is local to an instance of the function app, which will be different when the function scales out.
For such scenarios, D:\home is a better alternative as Azure Files is mounted here, which is the same across all instances.
As for the lock renewal error observed here, this issue tracks it but shouldn't cause issues as mentioned. If you do see any issue because of this, it would be best to share details in that issue.

P4API.net: how to use P4Callbacks delegates

I am working on a small tool to schedule p4 sync daily at specific times.
In this tool, I want to display the outputs from the P4API while it is running commands.
I can see that the P4API.net has a P4Callbacks class, with several delegates: InfoResultsDelegate, TaggedOutputDelegate, LogMessageDelegate, ErrorDelegate.
My question is: How can I use those, I could not find a single example online of that. A short example code would be amazing !
Note: I am quite a beginner and have never used delegates before.
Answering my own questions by an example. I ended up figuring out by myself, it is a simple event.
Note that this only works with P4Server. My last attempt at getting TaggedOutput from a P4.Connection was unsuccessful, they were never triggered when running a command.
So, here is a code example:
P4Server p4Server = new P4Server(syncPath);
p4Server.TaggedOutputReceived += P4ServerTaggedOutputEvent;
p4Server.ErrorReceived += P4ServerErrorReceived;
bool syncSuccess = false;
try
{
P4Command syncCommand = new P4Command(p4Server, "sync", true, syncPath + "\\...");
P4CommandResult rslt = syncCommand.Run();
syncSuccess=true;
//Here you can read the content of the P4CommandResult
//But it will only be accessible when the command is finished.
}
catch (P4Exception ex) //Will be caught only when the command has completely failed
{
Console.WriteLine("P4Command failed: " + ex.Message);
}
And the two methods, those will be triggered while the sync command is being executed.
private void P4ServerErrorReceived(uint cmdId, int severity, int errorNumber, string data)
{
Console.WriteLine("P4ServerErrorReceived:" + data);
}
private void P4ServerTaggedOutputEvent(uint cmdId, int ObjId, TaggedObject Obj)
{
Console.WriteLine("P4ServerTaggedOutputEvent:" + Obj["clientFile"]);
}

spring integration sftp channel

In sftp remote - I have 2 folder [ready] and [process] , What I need to do is first I have to move file from ready to process then I move that file to local directory using single channel .
Please check my code is this correct ?
my code works fine but I have doubt that first it moves to remote process or local folder which happening first ?
#Bean
public IntegrationFlow remoteToLocal() {
return IntegrationFlows
.from(Sftp.inboundAdapter(sftpSessionFactory())
.remoteDirectory(sftpProperties.getRemoteRootDir() + "/ready")
.regexFilter(FILE_PATTERN_REGEX)
.deleteRemoteFiles(true)
.localDirectory(new File(mmFileProperties.getMcfItes()+ mmFileProperties.getInboundDirectory()))
.preserveTimestamp(true)
.temporaryFileSuffix(".tmp"),
e -> e.poller(Pollers.fixedDelay(sftpProperties.getPollerIntervalMs()))
.id("sftpInboundAdapter"))
.handle(Sftp.outboundAdapter(mmSftpSessionFactory())
.remoteDirectory(sftpProperties.getRemoteRootDir() + "/process")
.temporaryFileSuffix(".tmp"))
.get();
}
Please check the new code but it it is not working
private StandardIntegrationFlow remoteToLocalFlow(final String localDirectory, final String remoteDirectoryProcessing, final String adapterName) {
return IntegrationFlows
.from(Sftp.inboundAdapter(mmSftpSessionFactory())
.remoteDirectory(remoteRootDir + remoteDirectoryProcessing)
.regexFilter(FILE_PATTERN_REGEX)
.deleteRemoteFiles(true)
.localDirectory(Paths.get(localDirectory).toFile())
.preserveTimestamp(true)
.temporaryFileSuffix(".tmp"),
e -> {
e.poller(Pollers.fixedDelay(mmSftpProperties.getPollerIntervalMs()))
.id(adapterName);
})
.handle(m -> logger.trace("File received from sftp interface: {}", m))
.handleWithAdapter(h -> h.sftpGateway(sftpSessionFactory(),AbstractRemoteFileOutboundGateway.Command.MV, "payload")
.renameExpression(remoteRootDir + ready)
.localDirectoryExpression(remoteRootDir + process)).get(); }
It looks ok, but it's not the best way to do it; you are copying the file, deleting it and sending it back with another name.
Use an SftpOutboundGateway with a MV (move) command instead.
You can also use a gateway to list and get files.

Raven DB 4.1.2 hangs on streaming query in Java

I have a jax-rs-based REST service that I run on Tomcat 8.5 on 64bit Linux, using Java 11; this service connects to a RavenDB 4.1.2 instance, also on the same Linux machine. I make use of the streaming query to return the request result. I use Postman to submit the same request, and everything works well: the results are returned, and rather quickly.
However - it only works 10 times. When I submit the same request as previously an 11th time, the results = currentSession.advanced().stream(query); line hangs and doesn't return.
At first I thought I could have something to do with the StreamingOutput or OutputStreamWriter not being closed appropriately. or perhaps something do to with the Response - but as I stepped through the deployed code in Eclipse in debug mode, I noticed that execution hangs on that streaming line.
(I find exactly 10 times to be a peculiarly "human choice" kind of number...)
The relevant parts of my code:
#GET
#Path("/abcntr/{ccode}/{st}/{zm}")
#Produces(MediaType.TEXT_PLAIN)
#Consumes(MediaType.TEXT_PLAIN)
public Response retrieveInfo(#PathParam("ccode") String ccode, #PathParam("st") String st, #PathParam("zm") String zm)
{
(...)
StreamingOutput adminAreaStream = new StreamingOutput()
{
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
#Override
public void write(OutputStream output) throws IOException, WebApplicationException
{
try(IDocumentSession currentSession = ServiceListener.ravenDBStore.openSession())
{
Writer writer = new BufferedWriter(new OutputStreamWriter(output));
(...)
if(indexToBeQueried.startsWith("Level0"))
{
IDocumentQuery<AdministrativeArea> query = currentSession.query(area.class, Query.index(indexToBeQueried))
.whereEquals("i", ccode);
results = currentSession.advanced().stream(query);
}
else
{
IDocumentQuery<AdministrativeArea> query = currentSession.query(area.class, Query.index(indexToBeQueried))
.whereEquals("i", ccode)
.andAlso()
.whereEquals("N1", sName);
results = currentSession.advanced().stream(query); // THIS IS WHERE IT DOESNT COME BACK
}
while(results.hasNext())
{
StreamResult<AdministrativeArea> adma = results.next();
adma.getDocument().properties = retrievePropertiesForArea(adma.getDocument(), currentSession);
writer.write(ow.writeValueAsString(adma.getDocument()));
writer.write(",");
}
(...)
currentSession.advanced().clear();
currentSession.close();
}
catch (Exception e)
{
System.out.println("Exception: " + e.getMessage() + e.getStackTrace());
}
}
};
if(!requestIsValid)
return Response.status(400).build();
else
return Response.ok(adminAreaStream).build();
}
The RavenDB error logs come up empty, as do the Tomcat error logs. The only thing that remotely resembles an error message relevant to this is something that shows up from "Gather debug info":
System.ArgumentNullException: Value cannot be null.
Parameter name: source
at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
at Raven.Server.Documents.Handlers.Debugging.QueriesDebugHandler.QueriesCacheList() in C:\Builds\RavenDB-Stable-4.1\src\Raven.Server\Documents\Handlers\Debugging\QueriesDebugHandler.cs:line 181
at Raven.Server.ServerWide.LocalEndpointClient.InvokeAsync(RouteInformation route, Dictionary`2 parameters) in C:\Builds\RavenDB-Stable-4.1\src\Raven.Server\ServerWide\LocalEndpointClient.cs:line 61
at Raven.Server.ServerWide.LocalEndpointClient.InvokeAndReadObjectAsync(RouteInformation route, JsonOperationContext context, Dictionary`2 parameters) in C:\Builds\RavenDB-Stable-4.1\src\Raven.Server\ServerWide\LocalEndpointClient.cs:line 91
at Raven.Server.Documents.Handlers.Debugging.ServerWideDebugInfoPackageHandler.WriteForDatabase(ZipArchive archive, JsonOperationContext jsonOperationContext, LocalEndpointClient localEndpointClient, String databaseName, String path) in C:\Builds\RavenDB-Stable-4.1\src\Raven.Server\Documents\Handlers\Debugging\ServerWideDebugInfoPackageHandler.cs:line 311
Thank you for any kinds of investigation hints you can give me.
UPDATE:
Same thing when moving the compiler and Tomcat JVM back to Java 1.8.
It appears that it has nothing to do with Java 11 (or 1.8), but simply that it had slipped my attention to close CloseableIterator<StreamResult<AdministrativeArea>> results; After adding a simple results.close(); everything appears to work as it should. If this wasn't the solution, I'll come back and update.

Extracting attachments from lotus notes api using EmbeddedObject, Creating eo*tm file in system folder

I am trying to extract attachments using EmbeddedObjects, I am able to extract attachments but create em*tm temp files in system temp folder.
EmbeddedObject embeddedObject=document.getAttachment(attachmentName);
InputStream inputStream=embeddedObject.getInputStream();
.....
......
inputStream.close();
embeddedObject..recycle();
document..recycle();
After closing input Stream its not deleting temp file form system temp folder.
Is it any thing wrong in my code or its setting issue with lotus notes.
Can you please help me in this?
Thanks for the help.
This is a common issue, and it relates to the incorrect closure/recycle of objects (either missing or out of sequence). E0*TM files will be created while the objects are alive and cleaned up when recycled.
If they are correct then check to see if any Antivirus software running that is blocking deletion.
The following sample code I used to test this before works, so compare to yours.
try {
System.out.println("Start");
String path = "test.txt";
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
System.out.println("Get DB");
Database db = session.getCurrentDatabase();
System.out.println("View + doc");
View vw = db.getView("main");
Document doc = vw.getFirstDocument();
System.out.println("Embedded object");
EmbeddedObject att = doc.getAttachment(path);
InputStream is = att.getInputStream();
ByteArrayOutputStream fos = new ByteArrayOutputStream();
byte buffer[] = new byte[(int) att.getFileSize()];
int read;
do {
read = is.read(buffer, 0, buffer.length);
if (read > 0) {
fos.write(buffer, 0, read);
}
} while (read > -1);
fos.close();
is.close();
// recycle the domino variables
doc.recycle();
vw.recycle();
db.recycle();
att.recycle();
} catch (Exception e) {
e.printStackTrace();
}
My suggestion would be to first comment out all the code that you represented in your post as
.....
......
Does the temp file still get left behind? If so, it looks like it's a bug in the Notes back end classes for 8.x that needs to be reported to IBM.
If not, then something in the commented-out code is preventing the the close() call from succeeding. InputStream is an abstract class, so perhaps you are binding inputStream to another type of stream object that must be closed in order to prevent the file from staying open.

Resources