I need to display on the screen a file that is returned by a web service that is in the format of a base 64 array. The ideal was to display it in a new browser window. I built a java agent to consume the web service.
I think an output would be an xagent, but I never implemented one, I do not know if that's the way, and how I would do that. Would a button on xpage call the xpage that would execute xagent? How would you consume the web service? Calling the xagent xpage agent? Impior both the java agent code for xagent? I've been researching how to do this fun all day, but so far I have not had much success.
Thanks a lot,
Marcus
The following XSnippet (of an XAgent) gives you a bit of an idea of what to do.
https://openntf.org/XSnippets.nsf/snippet.xsp?id=download-all-attachments
In the XSnippet above, all the attachments in a Document are being zipped and then sent to the browser as a zip file.
The url has some parameters e.g. documentID which the XAgent uses to determine which document to zip through.
The XAgent gets a handle to the HttpServletResponse, and configures it so that, instead of sending back an XPage, it specifies it is sending an 'application/zip' file.
It then finds the document using documentId and zips it up, writes the contents to the response and then tells the facesContext that the response is complete (don't do anymore rendering).
In your case, you would put a parameter which identifies the file that you want download. And you could link to this XAgent using a url e.g.
Download.xsp?fileId=somefileid
Your XAgent would then setup the response similar to above but the content type might not be an 'application/zip'. If you don't know the file type you can use 'application/octet-sream' but if you know it is a pdf of something you can use the appropriate Mime Type
Retrieve the file using whatever code you have written to access your webservice, decode it, and write it to the response's output stream
Example Implementing in Java as a Managed bean
The following example outputs some plain text that was originally in a base64 byte array. It is decoded and then written to the response.
All you would do is change the content type to 'application/octet-stream'
Create the managed bean in the Java Design Element.
package com.example;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;
import com.sun.faces.util.Base64;
public class DownloadBean implements Serializable {
private static final long serialVersionUID = 1L;
public DownloadBean() {
};
public void downloadFile() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext context = fc.getExternalContext();
String myFileName = "SomeFile.txt";
HttpServletResponse resp = (HttpServletResponse) context.getResponse();
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", -1);
// This example is just plain text but you would
// change this to 'application/octet-stream'
resp.setContentType("text/plain");
// Tell the browser it is an attachment with filename of <myFilename>
resp.setHeader("Content-Disposition", "attachment; filename="
+ myFileName);
OutputStream os = resp.getOutputStream();
// Somehow you need to get your byte[],
// that is up to you how you do that
// This example has just used base64 encoding of 'Hello Marcus'
byte[] base64bytes = "SGVsbG8gTWFyY3Vz".getBytes();
// Option 1 : use sun.misc.BASE64Decoder to decode with Streams
ByteArrayInputStream bais = new ByteArrayInputStream(base64bytes);
sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder();
dec.decodeBuffer(bais, os);
// Option 2 : use com.sun.faces.util.Base64 to decode to normal byte[]
//byte[] normalBytes = Base64.decode(base64bytes);
//os.write(normalBytes);
os.flush();
os.close();
fc.responseComplete();
}
}
Register it in faces-config.xml. Request Scope should be enough. so for example if you called it 'downloadBean', and it was of class com.example.DownloadBean, put this entry in faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
<managed-bean>
<managed-bean-name>downloadBean</managed-bean-name>
<managed-bean-class>com.example.DownloadBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
How to use
You can then call this from a button:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:button value="Download" id="buttonDownload">
<xp:eventHandler event="onclick" submit="true"
refreshMode="complete" action="#{downloadBean.downloadFile}">
</xp:eventHandler>
</xp:button>
</xp:view>
Or you can create an XPage like an XAgent that just downloads the file, and link to this Xpage in new window.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
rendered="false" beforePageLoad="#{downloadBean.downloadFile}">
</xp:view>
You could access url parameters if needed using something like
https://openntf.org/XSnippets.nsf/snippet.xsp?id=get-url-parameter-using-java
Related
I'm using the Gmail API in browser and want to allow the user to download email attachments. I see https://developers.google.com/gmail/api/v1/reference/users/messages/attachments/get but it returns JSON and base64 data. I don't think I can get that data in memory then trigger a "download" to save the file locally. Even if I could I don't think it would be efficient - it would probably download the file in memory vs. streaming it to a file. I think I need a direct link to a file that returns the correct file name and raw binary data (not base64). Is there a way to do this? Right now the only way I see is to proxy requests.
You can get the data from the base64 and save it to file locally.
If you are getting the attachment in Java, you can use the FileOutputStream class (or f.write() in Python) to write the bytes to file and save it locally with a path.
You can try with the following sample code from Google Developer page:
public static void getAttachments(Gmail service, String userId, String messageId)
throws IOException {
Message message = service.users().messages().get(userId, messageId).execute();
List<MessagePart> parts = message.getPayload().getParts();
for (MessagePart part : parts) {
if (part.getFilename() != null && part.getFilename().length() > 0) {
String filename = part.getFilename();
String attId = part.getBody().getAttachmentId();
MessagePartBody attachPart = service.users().messages().attachment().
get(userId, messageId, attId).execute();
byte[] fileByteArray = Base64.decodeBase64(attachPart.getData());
FileOutputStream fileOutFile =
new FileOutputStream("directory_to_store_attachments" + filename);
fileOutFile.write(fileByteArray);
fileOutFile.close();
}
}
}
So I want to create a java.io.File so that I can use it to generate a multipart-form POST request. I have the file in the form of a com.google.api.services.drive.model.File so I'm wondering, is there a way I can convert this Google File to a Java File? This is a web-app that uses the Google App Engine SDK, which prohibits every approach I've tried to make this work
No, you it doesn't seem like you can convert from com.google.api.services.drive.model.File to java.io.File. But it should still be possible to generate a multipart-form POST request using your data in Drive.
So the com.google.api.services.drive.model.File class is used for storing metadata about the file. It's not storing the file contents.
If you want to read the contents of your file into memory, this code snippet from the Drive documentation shows how to do it. Once the file is in memory, you can do whatever you want with it.
/**
* Download the content of the given file.
*
* #param service Drive service to use for downloading.
* #param file File metadata object whose content to download.
* #return String representation of file content. String is returned here
* because this app is setup for text/plain files.
* #throws IOException Thrown if the request fails for whatever reason.
*/
private String downloadFileContent(Drive service, File file)
throws IOException {
GenericUrl url = new GenericUrl(file.getDownloadUrl());
HttpResponse response = service.getRequestFactory().buildGetRequest(url)
.execute();
try {
return new Scanner(response.getContent()).useDelimiter("\\A").next();
} catch (java.util.NoSuchElementException e) {
return "";
}
}
https://developers.google.com/drive/examples/java
This post might be helpful for making your multi-part POST request from Google AppEngine.
In GoogleDrive Api v3 you can download the file content into your OutputStream. You need for that the file id, which you can get from your com.google.api.services.drive.model.File:
String fileId = "yourFileId";
OutputStream outputStream = new ByteArrayOutputStream();
driveService.files().get(fileId).executeMediaAndDownloadTo(outputStream);
I'm developing a web application using Google app engine for Java.
I will use Google Cloud storage and according to the documentation, I'm using GCS client library to emulate cloud storage on local disk.
I have no problem saving the files, I can see them from eclipse under the war folder (under the path WEB-INF/appengine-generated) and I can see them from the web admin panel accessible from the url
localhost:8888/_ah/admin
as indicated in this question
My question is the following. Which are the files URI under localhost to access them with GCS emulation?
Example of one of uploaded files on localhost:
file key is aglub19hcHBfaWRyJwsSF19haF9GYWtlQ2xvdWRTdG9yYWdlX18xIgpxcmNvZGUuanBnDA
ID/name is encoded_gs_key:L2dzLzEvcXJjb2RlLmpwZw
filename is /gs/1/qrcode.jpg
Thanks in advance.
You can see how this is done here:
https://code.google.com/p/appengine-gcs-client/source/browse/trunk/java/src/main/java/com/google/appengine/tools/cloudstorage/dev/LocalRawGcsService.java
As of today this mapping is being maintained by the using the local datastore. This may change in the future, but you should be able to simply call into this class or one of the higher level classes provided with the GCS client to get at the data.
Using getServingUrl()
The local gcs file is saved into a blob format.
When saving it, I can use location like your filename "/gs/1/qrcode.jpg"
Yet, when accessing it, this fake location is not working.
I found a way. It may not be the best, but works for me.
BlobKey bk = BlobstoreServiceFactory.getBlobstoreService().createGsBlobKey(location);
String url = ImagesServiceFactory.getImagesService().getServingUrl(bk);
The url will be like:
http://127.0.0.1:8080/_ah/img/encoded_gs_key:yourkey
(I was hardly to find any direct solution by google search.
I hope this answer can help others in need.)
Resource: ImagesServiceFactory ImageService
FileServiceFactory
For those who wish to serve the local GCS files that have been created by the GAE GCS library, one solution is to expose a Java Servlet like this:
package my.applicaion.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
public final class GoogleCloudStorageServlet
extends HttpServlet
{
#Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException
{
final BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
final String fileName = "/gs" + request.getPathInfo();
final BlobKey blobKey = blobstoreService.createGsBlobKey(fileName);
blobstoreService.serve(blobKey, response);
}
}
and in your web.xml:
<servlet>
<servlet-name>GoogleCloudStorage</servlet-name>
<servlet-class>my.applicaion.servlet.GoogleCloudStorageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GoogleCloudStorage</servlet-name>
<url-pattern>/gcs/*</url-pattern>
</servlet-mapping>
If you host this servlet in your GAE application, the URL for accessing a GCS file with bucket bucket-name and with name fileName is http://localhost:8181:/gcs/bucket-name/fileName, the local GAE development server port number being 8181.
This works at least from GAE v1.9.50.
And if you intend to have the local GCS server working in a unit test with Jetty, here is a work-around, hopefully with the right comments:
final int localGcsPortNumber = 8081;
final Server localGcsServer = new Server(localGcsPortNumber);
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
final String allPathSpec = "/*";
context.addServlet(new ServletHolder(new HttpServlet()
{
#Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
final BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
final String fileName = "/gs" + request.getRequestURI();
final BlobKey blobKey = blobstoreService.createGsBlobKey(fileName);
if (blobKey != null)
{
// This is a work-around over the "ServeBlobFilter" which does not take the "Content-Type" from the "blobInfo", but attempts to retrieve it from the "blobKey"
final BlobInfo blobInfo = BlobStorageFactory.getBlobInfoStorage().loadGsFileInfo(blobKey);
if (blobInfo != null)
{
final String contentType = blobInfo.getContentType();
if (contentType != null)
{
response.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
}
}
}
blobstoreService.serve(blobKey, response);
}
}), allPathSpec);
// The filter is responsible for taken the "blobKey" from the HTTP header and for fulfilling the response with the corresponding GCS content
context.addFilter(ServeBlobFilter.class, allPathSpec, EnumSet.of(DispatcherType.REQUEST));
// This attribute must be set, otherwise a "NullPointerException" is thrown
context.getServletContext().setAttribute("com.google.appengine.devappserver.ApiProxyLocal", LocalServiceTestHelper.getApiProxyLocal());
localGcsServer.setHandler(context);
localGcsServer.start();
I am trying to develop an API call using Apache CXF that takes in an attachment along with the request. I followed this tutorial and this is what I have got so far.
#POST
#Path("/upload")
#RequireAuthentication(false)
public Response uploadWadl(MultipartBody multipartBody){
List<Attachment> attachments = multipartBody.getAllAttachments();
DataHandler dataHandler = attachments.get(0).getDataHandler();
try {
InputStream is = dataHandler.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
return Response("OK");
}
I am getting an InputStream object to the attachment and everything is working fine. However I need to pass the attachment as a java.io.File object to another function. I know I can create a file here, read from the inputstream and write to it. But is there a better solution? Has the CXF already stored it as a File? If so I could just go ahead and use that. Any suggestions?
I'm also interested on this matter. While discussing with Sergey on the CXF mailing list, I learned that CXF is using a temporary file if the attachment is over a certain threshold.
In the process I discovered this blogpost that explains how to use CXF attachment safely.
You can be interested by the exemple on this page as well.
That's all I can say at the moment as I'm investigating right now, I hope that helps.
EDIT : At the moment here's how we handle attachment with CXF 2.6.x. About uploading a file using multipart content type.
In our REST resource we have defined the following method :
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Path("/")
public Response archive(
#Multipart(value = "title", required = false) String title,
#Multipart(value = "hash", required = false) #Hash(optional = true) String hash,
#Multipart(value = "file") #NotNull Attachment attachment) {
...
IncomingFile incomingFile = attachment.getObject(IncomingFile.class);
...
}
A few notes on that snippet :
#Multipart is not standard to JAXRS, it's not even in JAXRS 2, it's part of CXF.
In our code we have implemented bean validation (you have to do it yourself in JAXRS 1)
You don't have to use a MultipartBody, the key here is to use an argument of type Attachment
So yes as far as we know there is not yet a possibility to get directly the type we want in the method signature. So for example if you just want the InputStream of the attachment you cannot put it in the signature of the method. You have to use the org.apache.cxf.jaxrs.ext.multipart.Attachment type and write the following statement :
InputStream inputStream = attachment.getObject(InputStream.class);
Also we discovered with the help of Sergey Beryozkin that we could transform or wrap this InputStream, that's why in the above snippet we wrote :
IncomingFile incomingFile = attachment.getObject(IncomingFile.class);
IncomingFile is our custom wrapper around the InputStream, for that you have to register a MessageBodyReader, ParamHandler won't help as they don't work with streams but with String.
#Component
#Provider
#Consumes
public class IncomingFileAttachmentProvider implements MessageBodyReader<IncomingFile> {
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type != null && type.isAssignableFrom(IncomingFile.class);
}
#Override
public IncomingFile readFrom(Class<IncomingFile> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream
) throws IOException, WebApplicationException {
return createIncomingFile(entityStream, fixedContentHeaders(httpHeaders)); // the code that will return an IncomingFile
}
}
Note however that there have been a few trials to understand what was passed, how, and the way to hot-fix bugs (For example the first letter of the first header of the attachment part was eat so you had ontent-Type instead of Content-Type).
Of course the entityStream represents the actual InputStream of the attachment. This stream will read data either from memory or from disk, depending on where CXF put the data ; there is a size threshold property (attachment-memory-threshold) for that matter. You can also say where the temporary attachments will go (attachment-directory).
Just don't forget to close the stream when you are done (some tool do it for you).
Once everything was configured we tested it with Rest-Assured from Johan Haleby. (Some code are part of our test utils though) :
given().log().all()
.multiPart("title", "the.title")
.multiPart("file", file.getName(), file.getBytes(), file.getMimeType())
.expect().log().all()
.statusCode(200)
.body("store_event_id", equalTo("1111111111"))
.when()
.post(host().base().endWith("/store").toStringUrl());
Or if you need to upload the file via curl in such a way :
curl --trace -v -k -f
--header "Authorization: Bearer b46704ff-fd1d-4225-9dd4-e29065532b73"
--header "Content-Type: multipart/form-data"
--form "hash={SHA256}3e954efb149aeaa99e321ffe6fd581f84d5a497b6fab5c86e0d5ab20201f7eb5"
--form "title=fantastic-video.mp4"
--form "archive=#/the/path/to/the/file/fantastic-video.mp4;type=video/mp4"
-X POST http://localhost:8080/api/video/event/store
To finish this answer, I'd like to mention it is possible to have JSON payload in multipart, for that you can use an Attachment type in the signature and then write
Book book = attachment.getObject(Book.class)
Or you can write an argument like :
#Multipart(value="book", type="application/json") Book book
Just don't forget to add the Content-Type header to the relevant part when performing the request.
It might be worth to say that it is possible to have all the parts in a list, just write a method with a single argument of type List<Attachment>. However I prefer to have the actual arguments in the method signature as it's cleaner and less boilerplate.
#POST
void takeAllParts(List<Attachment> attachments)
I am using GWT and Google App Engine Java for my application. I have a profile screen where
user enters profile information like name, age and address, saves it and gets success or failure message. I developed this initial application using GWT-RPC and it worked fine. I had a new requirement where I have to store image of the user. I am using BlobstoreService to store images. This has created complications in the flow. I had to use FormPanel as it is the only way to do a FileUpload in GWT. The BlobStore service servlet expects a redirect on completion. As a result it cannot now return any status back to my GWT application once the profile is saved. Is there easy to store images using GWT along with other form fields and show a status message back to user once the profile is saved.
i struggled a lot with this problem until yesterday I figured out the solution with much help from Ikai Lan's blog. Basicaly what I did is follow his steps but with a few modifications because doing it exactly how he did it did'nt work for me:
Create a form panel : set encoding multipart, method post.
Make a GWT Remote Service that just has one method:public String getUploadURL() or something like that and in the IMPL write this:
BlobstoreService service = BlobstoreServiceFactory.getBlobstoreService();
return service.createUploadUrl("/XXX/YYY");
In XXX you must put your project path, for example mine is com.fer.pyn.PictureYourNews
In YYY you must put the servlet mapping name for a new servlet that we will have to create: I put XXX = BlobUploader, I created a BlobUploader extends HttpServlet and you have to update the web.xml.
Okey, so this is the weird part that I could'nt figure out, thing is that when we make a RPC call to getUploadURL() in the remote ervice from step 2 that returns a weird addres, like: '/_ah/img/eq871HJL_bYxhWQbTeYYoA' and that is the .fromAction you have to put in your form from step one. You need to update the form's action every time so i suggest the following:
public void initBlobStoreSession()
{
imageService.getBlobStoreUploadURL(new AsyncCallback()
{
#Override
public void onSuccess(String result) {
uploadFormPanel.setAction(result);
System.out.println("Upload Form Panel Action set");
}
#Override
public void onFailure(Throwable caught) {
//oops
}
});
}
So when you submit your fromPanel, IT WILL UPLOAD THE BLOB and you dont have to do anything, the tricky part is how to get the blob:
What you need to do now is create the YYY servlet we where talking about in step 4.
In the post method, this is important:
private BlobstoreService blobService = BlobstoreServiceFactory.getBlobstoreService();
Map<String, BlobKey> blobMap = blobService.getUploadedBlobs(request);
BlobKey blobKey = blobMap.get(UPLOAD_WIDJET_NAME);
UPLOAD_WIDJET_NAME is the .setName for the FileUpload widjet.
What you are doing there is getting a key for yout BLob so you can reference it later.
Our next step is showing the uploaded image back to the GWT layer:
//In the same post method from step 7
ImagesService imagesService = ImagesServiceFactory.getImagesService();
String imageURL = imagesService.getServingUrl(blobKey);
response.sendRedirect("/XXX/YYY?imgURL="+imageURL);
Now in the get method:
String imageUrl = request.getParameter("imgURL");
response.setHeader("Content-Type", "text/html");
response.getWriter().println(imageUrl);
We are done, now you just have to
uploadFormPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() {
#Override
public void onSubmitComplete(SubmitCompleteEvent event) {
uploadFormPanel.reset();
initBlobStoreSession();
String imageUrl = event.getResults();
Image image = new Image();
image.setUrl(imageUrl);
//if you are using jetty, leave this on
//or else it wont work
//Don't use GWT.getModuleBaseURL(), it doesnt
//work well in development mode
imageUrl.replace("http://0.0.0.0:8888/", "");
System.out.println(imageUrl);
final PopupPanel imagePopup = new PopupPanel(true);
imagePopup.setWidget(image);
// Add some effects
imagePopup.setAnimationEnabled(true); // animate opening the image
imagePopup.setGlassEnabled(true); // darken everything under the image
imagePopup.setAutoHideEnabled(true); // close image when the user clicks
imagePopup.center(); // center the image
}
});
check out upload4gwt which address uploading in GWT on AppEngine.
(disclosure: I created upload4gwt; it's not mature yet, however may be useful)
I had the same problem. As a workaround I'm using a redirection to a servlet that print a status message for the client to parse.
I'm passing the websafe string representation of the key to that result servlet.
That's a bit hackey, I'd like someone to come with a better answer, or explain why the blobstore servlet have to redirect.
Yeah, things get more complicated with uploads in GWT.
You can save the form data and image in separate RPCs, and either include a status message in the response to the image upload, or fire off a 3rd RPC when the form returns to get any status or metadata you need.