How to write an nt:file programmatically - aem

I'm trying to store binary data in JCR, which is created on the fly. My problem is that the only way provided by the JCR API is via an InputStream:
Session session = request.getResourceResolver().adaptTo(Session.class);
ValueFactory valueFactory = session.getValueFactory();
Binary bin = valueFactory.createBinary(is);
As CQ/Sling is RESTful I can see why this is the case as you usually get a form post or an httprequest to another source, where you always have an InputStream to use. But in my case I am creating the binary on the fly which usually is represented as an OutputStream.
Is there any other way I overlooked in the JCR API where I could create an OutputStream directly on the nt:file node, just like a FileOutputStream?
If no, is there an easy way to have an OutpuStream transformed to an InputStream?
I know the other way is available from the Apache Commons IOUtils.copy(). I've seen some examples on SO where they just use the ByteArrayOutputStream.toByteArray() to create an InputStream. But as the data could get rather large, this is not a good solution. Besides I tried it and somehow the stream was incomplete, so it seems there is a buffer limmit. The next approach was with piped streams, but there I have other problems to which I opened another question: Multiple quotes cause PipedOutputStream/OutputStreamWriter to fail
EDIT:
Removed PipedStream code example as I posted the issue with it in another question. So here I am still just looking for an easy way to create an nt:file where the input is not an InputStream.

Pipes are good solution here. However, in order to implement them properly, you have to use two threads: first should write data into the PipedOutputStream and the second should create a Binary from PipedInputStream and save it into JCR:
final PipedInputStream pis = new PipedInputStream();
final PipedOutputStream pos = new PipedOutputStream(pis);
Executors.newSingleThreadExecutor().submit(new Runnable() {
#Override
public void run() {
try {
OutputStreamWriter writer = new OutputStreamWriter(pos);
writer.append("append here some data");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Binary binary = session.getValueFactory().createBinary(pis);
session.getNode("/content/myNode").setProperty("xyz", binary);
session.save();
The symmetrical solution, in which you handle the JCR in the new thread would be also good.

Have you tried on of the methods described here?
http://ostermiller.org/convert_java_outputstream_inputstream.html
I think the easiest methods would be using an array:
ByteArrayOutputStream out = new ByteArrayOutputStream();
//alternatively, get the outputstream of your binary file generator
(...) //put data into out
InputStream is = new ByteArrayInputStream(out.toByteArray())

Related

Can we add ByteArrayInputStream in JobManager?

AEM6.2
I have a Osgi Service where in org.apache.sling.event.jobs.JobManager referenced and job is added to it.
The code is something like:
Map dataSourceMap = new HashMap<String, DataSource>
dataSourceMap.put(fileName, new ByteArrayDataSource(byte[], mimeTypeOfFile))
final Map<String, Object> props = new HashMap<String, Object>();
props.put("item1", "/something");
props.put("count", 5);
props.put("files", dataSourceMap)
jobManager.addJob("my/special/jobtopic", props);
When this job gets executed it shows some error
org.apache.sling.api.resource.PersistenceException: Value can't be stored in the repository: {<<filename>>=org.apache.commons.mail.ByteArrayDataSource#3f0f234c}
Question: Is there any solution to this exception? Or am I doing something wrong? can we add a ByteArrayInputStream to the jobmanager?
Thank you !
Just a info, If I remove the line props.put("files", dataSourceMap), it works fine.
Please let me know if you need more info on it.
Sling will store the job as a node in the repository and it looks like it only supports the "standard" types like String, Boolean, Integer etc. and not files/blobs.
I can not think of a way to add a file to the job, but what you could do is to create temporary node in the repository yourself, which contains the files/blobs.
Sling stores jobs here:
/var/eventing/jobs
You might do something similar:
/var/<project-name>/jobs
And the payload of the Sling job then contains the path to this job node.
Further to Jens' comment, the job will indeed store data as node properties in the JCR. You could likely explore the possibility of storing data as Binary to the jcr:data property, but I have not tested this myself.
As a quick and likely not very optimized workaround, why not serialize your byte[] to a String or even encode it to a Base64 string?
Sample: Base64 Java encode and decode a string [duplicate]

Changing jasper report parameters in runtime

I know, but we really need it.
We have a clear division of labor.
They create templates, I fill them in runtime according to some rules.
Can't teach my business to insert something like this and be sure they really did it ok(so can't move any logic to templates):
$P{risk_types}.get($F{risk_type}) ?: "UNDEFINED"
Also can not fill from files hardcoded in some adapter hadwritten by god-knows-who and unchangeable in runtime. It's a web app. Best option is to find a way to replace that file source from adapter to a ByteArrayStream.
SO:
Need to substitute contents of parameters(also default ones) at runtime.
example:
need to set JSON_INPUT_STREAM
Like this unsolved thread.
https://community.jaspersoft.com/questions/516611/changing-parameter-scriptlet
Really hope not to work on xml level, but xml also can't solve my problem as far as I tried.
Thank you!
The easiest and cleanest way we did this(bypassing usage of tons of deprecated documentation and unfinished bugged undocumented static antipatterned new features):
Create context with repository extension
SimpleJasperReportsContext jasperReportsContext = new SimpleJasperReportsContext();
jasperReportsContext.setExtensions(RepositoryService.class, Collections.singletonList(new MyRepositoryService(jasperReportsContext, yourOptionalParams)));
Fill this way(after compile and other usual actions)
JasperPrint print = JasperFillManager.getInstance(jasperReportsContext).fill(compiled, new HashMap<>());
Now your repository must extend default one to be hack-injected(cause of hodgie coded "isAssignableFrom") successfully
public class PrintFormsRepositoryService extends DefaultRepositoryService {
#Override
public InputStream getInputStream(RepositoryContext context, String uri) {
// return here your own good simple poj inputStream even from memory if you found source
// or pass to another repository service(default one probably)
return null;
}
}

RichEditDocumentServer docx conversion to pdf not working

I am trying to convert a word document(.docx) into PDF. I am trying to pass Stream object into load document method of RichEditDocumentServer,but my bad ,I am getting a blank pdf. I tried passing the file path in load document method ,which worked fine. But my requirement has to meet with stream object. Can anyone help me to fix the issue. A sample code has been added below.
private Stream ConvertToPdf(Stream fileStream)
{
RichEditDocumentServer server = new RichEditDocumentServer();
fileStream.Seek(0, SeekOrigin.Begin);
server.LoadDocument(fileStream, DocumentFormat.Doc);
Stream convertStream = new MemoryStream();
server.ExportToPdf(convertStream);
convertStream.Seek(0, SeekOrigin.Begin);
return convertStream;
}
it worked with the below link
https://www.devexpress.com/support/center/Question/Details/T340655#answer-9c3224dd-383a-4faa-9672-9b34e36c1c7a
server.LoadDocument(fileStream, DocumentFormat.OpenXml);

Axis2 How to create a service instance for other services

My service will load a lot of data (from txt files) to memory every request.
But,I want to keep the data in memory.
Because it is read from same txt files.
public class pirTMain {
public String[] RUN_pirT(...){
...
//this object will read txt files to initialize
ELC elc = new ELC(elcFolder.getPath());
//use elc to initialize a graph
pirT.initGraph(userID, nodeFile.getPath(), userScore, elc, true, begin, target);
//Use graph to search paths
itinerary = pirT.search(userID, TopK, begin, beginWithTime, target, targetWithTime);
...
I had read Axis2 document.
It says I can change service scope to "application".
But I still don't know how to do it, because I use eclipse plugin to generate a web service *.arr.
Can anyone suggest me how to separate elc object to another service?
Then, my pirTMain class can use it.
pirTMain is 'request'.
elc is 'application'.
thanks a lot.
There are a lot of different ways to achieve this, the simplest one that comes in my mind is to create a static reference to the read lines so that it's shared among all threads in the same virtual machine:
#WebService
public MyServiceClass {
private static String[] readLines = null;
private static synchronized getLines(){
if (readLines == null)
readLines = ....;
return readLines;
}
public int getNumberOfLines(){
return MyServiceClass.getLines().length;
}
public String getLine(int position){
return MyServiceClass.getLines()[position];
}
...
}
Probably not the "cleanest" way, but it works and it's easy to do. You could also wrap the login in a more standard "singleton pattern" if you prefer. keep in mind that getLines should be synchronized to be thread safe but if you experience bottlenecks remove the synchronized keyword, you could get useless reads on first calls but it would be faster.

Chunk reading in Spring Batch - not only chunk writing

My assumption
In my understanding "chunk oriented processing" in Spring Batch helps me to efficiently process multiple items in a single transaction. This includes efficient use of interfaces from external systems. As external communication includes overhead, it should be limited and chunk-oriented too. That's why we have the commit-level for the ItemWriter.
So what I don't get is, why does the ItemReader still have to read item-by-item? Why can't I read chunks also?
Problem description
In my step, the reader has to call a webservice. And the writer will send this information to another webservice. That's why I wan't to do as few calls as necessary.
The interface of the ItemWriter is chunk-oriented - as you know for sure:
public abstract void write(List<? extends T> paramList) throws Exception;
But the ItemReader is not:
public abstract T read() throws Exception;
As a workaround I implemented a ChunkBufferingItemReader, which reads a list of items, stores them and returns items one-by-one whenever its read() method is called.
But when it comes to exception handling and restarting of a job now, this approach is getting messy. I'm getting the feeling that I'm doing work here, which the framework should do for me.
Question
So am I missing something? Is there any existing functionality in Spring Batch I just overlooked?
In another post it was suggested to change the return type of the ItemReader to a List. But then my ItemProcessor would have to emit multiple outputs from a single input. Is this the right approach?
I'm graceful for any best practices. Thanks in advance :-)
This is a draft for an implementation of the read() interface method.
public T read() throws Exception {
while (this.items.isEmpty()) {
final List<T> newItems = readChunk();
if (newItems == null) {
return null;
}
this.items.addAll(newItems);
}
return this.items.pop();
}
Please note, that items is a buffer for the items read in chunks and not requested by the framework yet.
Spring Batch uses 'Chunk Oriented' processing style. (Not just chunk read or write, full process including read, process and write)
Chunk oriented processing refers to
Read an item using ItemReader (Single Item)
Process it using ItemProcessor, and aggregate the result (Result List is updated one by one).
Once the commit interval is reached, the entire aggregated result (Result List) is written out using ItemWriter and then the transaction is committed.
Here is the code representation from SpringBatch doc
List items = new Arraylist();
for(int i = 0; i < commitInterval; i++){
Object item = itemReader.read()
Object processedItem = itemProcessor.process(item);
items.add(processedItem);
}
itemWriter.write(items);
As you said, if you need your reader to return multiple Items, make it a List. And if your processor also returns a List. Finally, your Writer will get a List of List.
Here is the code representation of the new case
List<List<Object>> resultList = new Arraylist<List<Object>>();
for(int i = 0; i < commitInterval; i++){
List<Object> items = itemReader.read()
List<Object> processedItems = itemProcessor.process(items);
resultList.add(processedItems);
}
itemWriter.write(resultList);