ColdFusion/CFWheels Merge Multiple PDFs in different Controllers - pdf

I'm using Coldfusion 10 and CFWheels for my site.
Basically my site has a bunch of different types of forms with their own Controllers and views. For each form, the user has the option to dynamically generate a PDF of the form and download it. It basically loads the controller data but when it hits the view with a parameter of "pdf" it does the following which will generate the PDF and open the document in the browser:
<cfdocument format="PDF" saveAsName="#formtype#_#id#.pdf">
#includePartial("/printView")#
</cfdocument>
Each of these PDFs can have multiple pages depending on how many line items are added. Like I said in the beginning there are multiple types of forms so they will have their own controller and views and PDF generation with their print views. These forms are all customized and associated together with an ID like shipmentID. So I can have one shipment that contains 2 forms of type A and 1 form of type B and 3 of type C, etc. What I need to do is generate 1 PDF with all the forms merged together based on the shipment. So taking my example, the merged PDF for the shipment would contain the 2 forms of type A, 1 form of type B, and 3 of type C all merged.
Currently what I'm doing is making a http "GET" call to each of the dynamically generated PDF pages, save that to a temp directory, then merging them at the end.
I load the shipment and for each different type of form I do the following where urlPath is the path to the view that generates the dynamic PDF:
var httpService = new http();
httpService.setMethod("GET");
httpService.setUrl(urlPath);
invoice = httpService.send().getPrefix().filecontent.toByteArray();
var fullPath = "#filePath##arguments.type#_#id#.pdf";
//write files in temp directory
FileWrite(fullPath, invoice);
After I get the PDF and write it to a file, I save the path in an array for reference so I can loop through and merge all the referenced files in the array, then delete the temp directory where the files were saved.
The reason why I'm doing it this way is because the controllers and views are already set and generate the individual PDFs on the fly as it is.
If I try to load (all associated forms) and put everything in one file, I'll have to add all the same controller logic to load each form specific stuff and the associated views but these already exist for the individual page view.
Is there a better way to do this?
It works fine if there are only a few PDFs but if there a lot of different forms in the shipment like 20, then it's very slow and since we don't have CF Enterprise, I believe the cfdocument is single threaded. The forms have to be generated dynamically so they contain the most current data.
UPDATE for Chris
I've added some code to show what the various forms might look like. I validate and load a bunch of other things but I stripped it down to get the general idea:
controllers/Invoices.cfc
The path might be something like: /shipments/[shipmentkey]/invoices/[key]
public void function show(){
// load shipment to display header details on form
shipment = model("Shipment").findOne(where="id = #params.shipmentkey#");
// load invoice details to display on form
invoice = model("Invoice").findOne(where="id = #params.key#");
// load associated invoice line items to display on form
invoiceLines = model("InvoiceLine").findAll(where="invoiceId = #params.key#");
// load associated containers to display on form
containers = model("Container").findAll(where="invoiceid = #params.key#");
// load associated snumbers to display on form
scnumbers = model("Scnumber").findAll(where="invoiceid = #params.key#");
}
controllers/Permits.cfc
The path might be something like: /shipments/[shipmentkey]/permits/[key]
public void function show(){
// load shipment to display header details on form
shipment = model("Shipment").findOne(where="id = #params.shipmentkey#");
// load permit details to display on form
permit = model("Permit").findOne(where="id = #params.key#");
// load associated permit line items to display on form
permitLines = model("PermitLine").findAll(where="permitId = #params.key#");
}
controllers/Nafta.cfc
The path might be something like: /shipments/[shipmentkey]/naftas/[key]
public void function show(){
// load shipment to display header details on form
shipment = model("Shipment").findOne(where="id = #params.shipmentkey#");
// load NAFTA details to display on form
nafta = model("NAFTA").findOne(where="id = #params.key#");
// load associated NAFTA line items to display on form
naftaLines = model("NaftaLine").findAll(where="naftaId = #params.key#");
}
Currently my view is based on a URL parameter called "view" where the values can be either "print" or "pdf".
print - displays the print view that's pretty much a stripped down version of the form without the webpage headers/footers etc.
pdf - calls the cfdocument code I pasted at the top of the question which uses the printView to generate the PDF.
I don't think I need to post the "show.cfm" code as it would just be a bunch of divs and tables displaying the specific information for each particular form in question.
Keep in mind that these are only 3 example form types and there are 10+ types that may be associated to 1 shipment and the PDF's would need to be merged. Each type may repeat several times within a shipment as well. For example a shipment may contain 10 different invoices with 5 permits and 3 NAFTAs.
To make things slightly more complicated, a shipment can have 2 types: US Bound or Canada Bound and based on this different form types can be associated to the shipment. So an Invoice for Canada will have totally different fields than an invoice for US so the models/tables are different.
Currently to do the merging I have a controller that does something like the following (note that I stripped a lot of validation, loading of other objects to simplify)
public any function displayAllShipmentPdf(shipmentId){
// variable to hold the list of full paths of individual form PDFs
formList = "";
shipment = model("shipment").findOne(where="id = #arguments.shipmentId#");
// path to temporarily store individual form PDFs for later merging
filePath = "#getTempDirectory()##shipment.clientId#/";
if(shipment.bound eq 'CA'){
// load all invoices associated to shipment
invoices = model("Invoice").findAll(where="shipmentId = #shipment.id#");
// go through all associated invoices
for(invoice in invoices){
httpService = new http();
httpService.setMethod("get");
// the following URL loads the invoice details in the Invoice controller and since I'm passing in "view=pdf" the view will display the PDF inline in the browser.
httpService.setUrl("http://mysite/shipments/#shipment.id#/invoices/#invoice.id#?view=pdf");
invoicePdf = httpService.send().getPrefix().fileContent.toByteArray();
fullPath = "#filePath#invoice_#invoice.id#.pdf";
// write the file so we can merge later
FileWrite(fullPath, invoicePdf);
// append the fullPath to the formList as reference for later merging
formList = ListAppend(formList, fullPath);
}
// the above code would be similarly repeated for every other form type (ex. Permits, NAFTA, etc.). So it would call the path with the "view=pdf" which will load the specific form Controller and display the PDF inline which we capture and create a temporary PDF file and add the path to the formList for later merging. You can see how this can be a long process as you have several types of forms associated to a shipment and there can be numerous forms of each type in the shipment and I don't want to have to repeat each form Controller data loading logic.
}else if(shipment.bound eq 'US'){
// does similar stuff to the CA except with different forms
}
// merge the PDFs in the formList
pdfService = new pdf();
// formList contains all the paths to the different form PDFs to be merged
pdfService.setSource(formList);
pdfService.merge(destination="#filePath#shipment_#shipment.id#.pdf");
// read the merged PDF
readPdfService = new pdf();
mergedPdf = readPdfService.read(source="#filePath#shipment_#shipment.id#.pdf");
// delete the temporarily created PDF files and directory
DirectoryDelete(filePath, "true");
// convert to binary to display inline in browser
shipmentPdf = toBinary(mergedPdf);
// set the response to display the merged PDF
response = getPageContext().getFusionContext().getResponse();
response.setContentType('application/pdf');
response.setHeader("Content-Disposition","filename=shipment_#shipment.id#_#dateFormat(now(),'yyyymmdd')#T#timeFormat(now(),'hhmmss')#.pdf");
response.getOutputStream().writeThrough(shipmentPdf);
}

See: https://forums.adobe.com/thread/1121909 ... "...Standard Edition Adobe throttles the PDF functions to a single thread,...Developer runs like Enterprise" so your development environment will whip out the pdfs but your CF Standard production server will be choking.
Also, seems you are not having trouble with one or two pdfs. I have CF Enterprise and it was generating pdfs just fine - a few seconds - and then out of nowhere pdfs started taking 4 minutes. Another comment in the above referenced adobe post suggested check in the /etc/hosts that CF is contacting itself (?????). Well some digging and I found that the Windows\system32\drivers\etc\hosts had been updated a day before users discovered pdfs were timing out. The IP had been changed to some other intranet IP and the server name was the DNS server name. I changed the value back to 127.0.0.1 localhost and voila, pdfs started rendering in normal amounts of time.

Related

GridFs read PDF

I am trying to build a financial dashboard with Flask and pymongo. The starting point is a flask form which saves data in a MongoDB database. One of the fields in the form is a FileField (wtforms) which allows the upload of a PDF, which is then stored in MongoDB with GridFS.
Now I manage to save the pdf and I can see the resulting entries within the .files and .chunks collections. Now I would like to build a function that retrieves the PDFs and analyses them with some basic NLP, however I struggle with the getting meaningful data.
When I do:
storage = gridfs.GridFS(db, collection)
data = storage.get('some id')
a = data.read()
The result is a binary file. If I continue with:
with open(data, 'rb') as f:
b = f.read()
The result is "ValueError: embedded null byte or sometimes an empty "byte string".
Any help on this?
To follow up on the above, I found a solution for myself that consists in 2 separate functions:
(1) Upon upload of the form and before uploading the files to MongoDB, I apply a function based on pdfminer that extracts the string content of the PDF and tranform it into a list of sentences using NLTK. I will then store this list in the .files via the storage.put(file, sent_list = sent_list) #sent_list being the variable name of the list of sentences.
Whenever I wish to run NLP operations on the file, I will just call the "sent_list" variable from mongodb.
(2) If I wish to display the stored pdf in its original content however, I included the following function as a separate route.
storage = GridFS(db, collection)
data = storage.get_last_version(filename)
response = make_response(data.read())
extension = data.filename.split('.')[-1]
response.headers['Content-Type'] = f'application/{extension}'
response.headers['Content-Disposition'] = f'inline; filename={data.filename}'
return response
(2) will open a new tab in my flask app showing the .pdf file in its original format.
I hope this helps anyone coming across a similar problem in the future.

Reload just one tab in Qlikview

How can we reload one tab , means one request of many in QLikview.
PS:
I have one request per tab
Help appreciated
Thank you
If you are talking about 1 tab in the front end of QlikView you cannot refresh it as the entire model is driven by the underlying data and data structure.
You can set up partial reloads that will reload only certain tables of in that data.
One request per tab is not ideal, recommended practice is that separate data is held in separate Qlikview documents.
However, this can be achieved - using variables and locally stored QVD's and the script below. You can also control variable values on the UI using buttons and actions on objects. The QVD's hold the data and are only reloaded when expressly requested.
//choose which data to load 1 = load query from scratch
Let vLoadQuery1=1;
Let vLoadQuery2=0;
Let vLoadQuery3=0;
Let vLoadQuery4=0;
Let vLoadQuery5=0;
if vLoadQuery1=1 then
//Enter load details and capture in local QVD
//Load
Query1:
LOAD *;
SQL select * from some.data;
//Store
STORE * from Query1 into Query1.qvd (qvd);
Drop Table Query1;
ENDIF
//Load from QVD - if this hasn't been loaded on this reload, it will grab the previous data
NewQuery1:
LOAD *
FROM Query1.qvd (qvd);

Qlik View: Retrive Last Reload of another report

In a qlik report(Main Report) i have a text object with an action that open another report. Is there the possibility to show in the text object located in the Main Report the last Reloadtime date of the report that i open with the action ?
thank's
while reloading the other report,
use:
T1:
Load reloadtime() as reloadTime autogenerate(1);
store T1 into T1.qvd;
in the Main report file:
Load * from T1.qvd (qvd);
you can also use *add* Load * from T1.qvd (qvd); if you want to use partial reload.
thats it :)
i can use filetime() function but the last change is not necessarily the date of the last reloadtime().
I could store these information externally in a txt-file which you read in your target-application as include-variable. This will require a reload from the target. As alternatively could these value be stored in a database and read then per direct discovery without a reload.
A further option could be to transfer these information per selections by opening: AJAX and URL parameters maybe by selection a loosen dummy-date-table and these selection will then be queried per getfieldselections().

Synchronize modification between SWT table and TextEditor

I'm facing a problem and want to ask for a solution.
I'm working on an eclipse plugin project, in which an editor for a type of resource file is required. The resource file has similar structure like CSV file. My idea is to provide user the option to edit this type of file both in plain text format and also in an SWT table. Plain text is required for examining data and table provides more flexibility to editing such as sorting by column.
I have been able to create a MultiPageEditorPart, with one page of org.eclipse.ui.editors.text.TextEditor, and another page with a org.eclipse.swt.widgets.Table and several other widgets like search bar. The content of the resource file can be shown in the TextEditor, can also be edited and saved. On the other hand, the content can be loaded in the table too, sorting and searching all work good.
The problem is: when I edit a cell in the table, I want the change also reflected in the TextEditor, and vice versa. Since the resource file can be very large, I want the saving action happen only on the TextEditor, i.e. I don't want any modification in the table directly stored to resource file, but to mark the file dirty, but I can't figure out how. How can I for example get the content of EditorInput, check it line by line, and modify it outside TextEditor?
Or, are there more efficient ways to do this? Can anyone give any hints?
The IDocument used by the TextEditor gives you access to the document contents. Get this with something like:
IDocumentProvider provider = editor.getDocumentProvider();
IEditorInput input = editor.getEditorInput();
IDocument document = provider.getDocument(input);
IDocument has many methods for accessing lines such as:
int getLineOffset(int line);
int getLineLength(int line);
and methods for modify the text:
void replace(int offset, int length, String text);

Ektron Workarea

I need to develop an application that extracts all the contents in Content Tab of the Ektron Workarea and I have to keep tree structure of folders (taxonomies,collections,forms,etc.) also.When I click the content I need to get the Content ID in the code behind also.I need to do all these in a single function.
I tried this requirement with the concept of content block widget in workarea.When we drag that widget and edit it a pop up will come and it displays the folders of work area in tree structure.But when I created an aspx page, put the same code and I browse that page I didn't get the tree structure of all contents.Only the main tabs(Folders,Taxonomies and search ) are visible.Then I drag the user control in the aspx page .But it also doest work.
So how will I solve the above problem.
Can I pull all the contents in tree structure from work area from the root using API codes?.Then can anyone please give the API code to solve?
Please anyone reply!
Assuming you are using 8.6 look here to start with:
http://reference.ektron.com/developer/framework/content/contentmanager/getlist.aspx
Update:
I think I misread your question the first time around. Allow me to expand on my answer a bit. My original answer with the web services assumes that you are rendering the content tree from some sort of "presentation tier" -- a different web site, a console app, or a WPF/WinForms app, etc.
You can get the recursive folder structure with something like this:
private FolderData GetFolderWithChildren(long folderId)
{
var folderApi = new Ektron.Cms.API.Folder();
var folderData = folderApi.GetFolder(folderId);
// This next method is marked as obsolete in v9.0;
// a newer overload is available in v9.0, but I
// don't know if it's available in v8.0
folderData.ChildFolders = folderApi.GetChildFolders(folderId, true);
}
I'm a little confused as to what exactly you're trying to accomplish. If you want to show the entire tree structure graphically, have you tried taking the code and markup from the edit view of the content widget and using it on your non-edit view?
I must say, your requirement that "I need to do all these in a single function" worries me a bit. Workarea content trees can get really large very quickly. If you're trying to load all of the folders and all the taxonomies and all the collections, etc. Then the user will likely be waiting a long time for the page to load, and you risk running into timeout issues.
-- Original Answer --
Ektron v8.0 doesn't have the 3-tier option, which is too bad because that would really make your job a lot easier. In v8.0, there are ASMX web services that you can reference, including:
/workarea/webservices/content.asmx
/workarea/webservices/webserviceapi/user/user.asmx
There are lots more than this; browse through the folders within /workarea/ to see what's available.
It's been a while since I've worked with these services, so I'm a little rusty...
Suppose you add references to those two services I listed above and name them ContentService and UserService. The first thing you'll want to do is set the authentication headers. Then you can call the service methods in much the same way as the old legacy apis.
var contentApi = new ContentService.Content();
contentApi.AuthenticationHeaderValue = new ContentService.AuthenticationHeader();
contentApi.AuthenticationHeaderValue.Username = username;
contentApi.AuthenticationHeaderValue.Password = password;
contentApi.AuthenticationHeaderValue.Domain = domain;
var userApi = new UserService.User();
userApi.AuthenticationHeaderValue = new UserService.AuthenticationHeader();
userApi.AuthenticationHeaderValue.Username = username;
userApi.AuthenticationHeaderValue.Password = password;
userApi.AuthenticationHeaderValue.Domain = domain;
var ud = userApi.GetUserbyUsername("jimmy456");
long folderID = 85;
bool recursive = true;
ContentData[] folderContent = contentApi.GetChildContent(folderID, recursive, "content_id");