package er.prototaculous.widgets;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOMultipartIterator;
import com.webobjects.appserver.WOMultipartIterator.WOFormData;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import er.extensions.appserver.ERXResponseRewriter;
import er.extensions.appserver.ajax.ERXAjaxApplication;
import er.extensions.foundation.ERXFileUtilities;
import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXStringUtilities;
/**
* Encapsulation of @see <a href="http://github.com/valums/ajax-upload">Ajax Upload</a>
* @binding name
* @binding onChange
* @binding onSubmit
* @binding onComplete
*
* @property useUnobtrusively For Unobtrusive Javascript programming. Default it is off.
*
* @author mendis
*
*/
public abstract class AjaxUpload extends WOComponent {
private static boolean useUnobtrusively = ERXProperties.booleanForKeyWithDefault("er.prototaculous.useUnobtrusively", false);
public AjaxUpload(WOContext aContext) {
super(aContext);
}
/*
* Bindings/API
*/
public static interface Bindings {
public static final String id = "id";
public static final String name = "name";
public static final String onChange = "onChange";
public static final String onSubmit = "onSubmit";
public static final String onComplete = "onComplete";
public static final String filePath = "filePath";
public static final String data = "data";
public static final String mimeType = "mimeType";
public static final String inputStream = "inputStream";
public static final String outputStream = "outputStream";
public static final String finalFilePath = "finalFilePath";
public static final String streamToFilePath = "streamToFilePath";
}
/*
* Headers of multipart form
*/
public static interface Headers {
public static final String filename = "filename";
public static final String contentType = "content-type";
public static final String name = "name";
}
@Override
public boolean synchronizesVariablesWithBindings() {
return false;
}
// accessors
public String id() {
return _id() == null ? "au" + ERXStringUtilities.safeIdentifierName(elementID()) : _id();
}
private String elementID;
private String elementID() {
if (elementID == null) elementID = context().elementID();
return elementID;
}
private String _id() {
return (String) valueForBinding(Bindings.id);
}
private String _script() {
return "new AjaxUpload('" + id() + "', {" + options() + "});";
}
public String script() {
return isAjax() ? _script() : "document.observe('dom:loaded', function() { " + _script() + " });";
}
private boolean isAjax() {
return ERXAjaxApplication.isAjaxRequest(context().request());
}
/*
* An array of options for Ajax.Updater
*/
protected NSArray<String> _options() {
NSMutableArray _options = new NSMutableArray("action:'" + href() + "'");
// add options
if (hasBinding(Bindings.name)) _options.add("name:'" + valueForBinding(Bindings.name) + "'");
if (hasBinding(Bindings.onChange)) _options.add("onChange:" + valueForBinding(Bindings.onChange));
if (hasBinding(Bindings.onComplete)) _options.add("onComplete:" + valueForBinding(Bindings.onComplete));
if (hasBinding(Bindings.onSubmit)) _options.add("onChange:" + valueForBinding(Bindings.onSubmit));
return _options.immutableClone();
}
public String options() {
return _options().componentsJoinedByString(", ");
}
private String _uploadName() {
return (String) valueForBinding(Bindings.name);
}
public String uploadName() {
return _uploadName() != null ? _uploadName() : "userfile"; // Default
}
protected void setFilePath(String aPath) {
setValueForBinding(aPath, Bindings.filePath);
}
protected void setData(NSData data) {
setValueForBinding(data, Bindings.data);
}
protected void setMimeType(String aType) {
setValueForBinding(aType, Bindings.mimeType);
}
/*
* NOTE: this is a standard WO component action url.
* The file upload won't work as an ajax request.
*
*/
private String href() {
return context().componentActionURL();
}
// R&R
@Override
public void appendToResponse(WOResponse response, WOContext context) {
super.appendToResponse(response, context);
if (!useUnobtrusively) {
ERXResponseRewriter.addScriptResourceInHead(response, context, "Ajax", "prototype.js");
ERXResponseRewriter.addScriptResourceInHead(response, context, "Ajax", "scriptaculous.js");
ERXResponseRewriter.addScriptResourceInHead(response, context, "ERPrototaculous", "ajaxupload.3.5.js");
}
}
@Override
public void takeValuesFromRequest(WORequest request, WOContext context) {
super.takeValuesFromRequest(request, context);
if (request.formValueForKey(uploadName()) != null) {
// filepath
if (hasBinding(Bindings.filePath)) {
setFilePath((String) request.formValueForKey(uploadName() + ".filename"));
}
// file data
if (hasBinding(Bindings.data)) {
if (hasBinding(Bindings.filePath)) {
NSArray aValue = request.formValuesForKey(uploadName());
if (aValue != null) {
NSData data = null;
try {
data = (NSData) aValue.objectAtIndex(0);
} catch (ClassCastException e) {
throw new ClassCastException("AjaxUploadButton: Value in request was of type '" + aValue.objectAtIndex(0).getClass().getName() + "' instead of NSData. Verify that the WOForm's 'enctype' binding is set to 'multipart/form-data'");
}
setData(data);
}
// mimetype
if (hasBinding(Bindings.mimeType)) {
setMimeType((String) request.formValueForKey(uploadName() + ".mimetype"));
}
}
} else {
// multipart data
WOMultipartIterator multipartIterator = request.multipartIterator();
WOFormData nextFormData = multipartIterator.nextFormData();
NSDictionary<Object, String> contentDispositionHeaders;
do {
if (nextFormData == null)
break;
contentDispositionHeaders = nextFormData.contentDispositionHeaders();
Object _name = contentDispositionHeaders.objectForKey(Headers.name);
if (uploadName().equals(_name))
break;
nextFormData = multipartIterator.nextFormData();
} while(true);
if(nextFormData == null)
throw new IllegalStateException("AjaxUploadButton: No form data left for WOFileUpload!");
contentDispositionHeaders = nextFormData.contentDispositionHeaders();
String aFileName = null;
if(hasBinding(Bindings.filePath)) {
aFileName = (String) contentDispositionHeaders.valueForKey(Headers.filename);
setFilePath(aFileName);
}
if (hasBinding(Bindings.mimeType)) {
setMimeType((String) contentDispositionHeaders.valueForKey(Headers.contentType));
}
InputStream anInputStream = nextFormData.formDataInputStream();
if(aFileName != null && aFileName.length() > 0) {
if(hasBinding(Bindings.inputStream)) {
setValueForBinding(anInputStream, Bindings.inputStream);
} else {
String localFilePath = null;
File tempFile = null;
if (!hasBinding(Bindings.outputStream)) {
if (hasBinding(Bindings.finalFilePath))
setValueForBinding(null, Bindings.finalFilePath);
try {
tempFile = ERXFileUtilities.writeInputStreamToTempFile(anInputStream, context.session().sessionID(), ".tmp");
} catch (IOException e) {
throw new RuntimeException("Couldn't write input stream to temp file: " + e);
} finally {
try { anInputStream.close(); } catch (IOException e) {}
}
} else {
OutputStream anOutputStream = (OutputStream) valueForBinding(Bindings.outputStream);
try {
ERXFileUtilities.writeInputStreamToOutputStream(anInputStream, anOutputStream);
} catch (IOException e) {
throw new RuntimeException("Couldn't write input stream to output stream: " + e);
} finally {
try { anOutputStream.close(); } catch (IOException e) {}
try { anInputStream.close(); } catch (IOException e2) {}
}
}
if (hasBinding(Bindings.streamToFilePath)) {
localFilePath = (String) valueForBinding(Bindings.streamToFilePath);
try {
ERXFileUtilities.renameTo(tempFile, new File(localFilePath));
setValueForBinding(localFilePath, Bindings.finalFilePath);
} catch (Exception e) {
setValueForBinding(tempFile.getPath(), Bindings.finalFilePath);
throw new RuntimeException("Couldn't rename temp file: " + e);
}
}
}
} else {
if (hasBinding(Bindings.inputStream))
setValueForBinding(null, Bindings.inputStream);
if (hasBinding(Bindings.finalFilePath))
setValueForBinding(null, Bindings.finalFilePath);
byte buffer[] = new byte[128];
try {
while (anInputStream.read(buffer) != -1) ;
}
catch (IOException e) {
throw new RuntimeException("Error skipping empty file upload: " + e);
} finally {
try { anInputStream.close(); } catch (IOException e2) {}
}
}
}
}
}
}