package fr.openwide.core.wicket.more.fileapi.behavior;
import java.io.IOException;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxCallListener;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.attributes.CallbackParameter;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.cycle.RequestCycle;
import org.wicketstuff.wiquery.core.javascript.JsStatement;
import org.wicketstuff.wiquery.core.javascript.JsUtils;
import org.wicketstuff.wiquery.core.options.LiteralOption;
import org.wicketstuff.wiquery.core.options.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.openwide.core.wicket.more.fileapi.model.FileApiFile;
import fr.openwide.core.wicket.more.fileapi.model.FileUploadFailType;
import fr.openwide.core.wicket.more.fileapi.model.FileUploadMode;
import fr.openwide.core.wicket.more.markup.html.template.js.jquery.plugins.fileuploadglue.FileUploadGlueJavaScriptResourceReference;
import fr.openwide.core.wicket.more.markup.html.template.js.jquery.plugins.util.JsScopeFunction;
public abstract class FileUploadBehavior extends AbstractDefaultAjaxBehavior {
private static final long serialVersionUID = -5670865313571005330L;
private static final Logger LOGGER = LoggerFactory.getLogger(FileUploadBehavior.class);
/**
* nom du paramètre utilisé pour transmettre les fichiers
*/
public static final String PARAMETERS_FILE_UPLOAD = "files";
/**
* nom du paramètre utilisé pour transmettre les informations issues de file API (métadonnées)
*/
private static final String PARAMETERS_FILE_LIST = "fileList";
/**
* nom du paramètre utilisé pour transmettre les informations à la fin de l'upload (fichiers uploadés avec succès)
*/
private static final String PARAMETERS_UPLOAD_DONE_SUCCESS_FILE_LIST = "successFileList";
/**
* nom du paramètre utilisé pour transmettre les informations à la fin de l'upload (fichiers non uploadés)
*/
private static final String PARAMETERS_UPLOAD_DONE_ERROR_FILE_LIST = "errorFileList";
/**
* nom du paramètre utilisé pour transmettre les informations à la fin de l'upload (fichiers non uploadés)
*/
private static final String PARAMETERS_UPLOAD_FAILS_ERROR_MESSAGE = "uploadFailsErrorMessage";
/**
* nom de la variable globale d'échange ; permet de communiquer dans informations javascript
* via le AjaxRequestTarget.
*/
private static final String PARAMETERS_DATA_VARIABLE_NAME = "dataVariableName";
/**
* paramètre qui stocke le mode (permet de dispatcher les appels ajax)
*/
private static final String PARAMETERS_MODE = "mode";
/**
* paramètre qui stocke le le type d'échec
*/
private static final String PARAMETERS_FAIL_TYPE = "failType";
/**
* nom de l'option qui stocke le nom de paramètre POST utilisé pour transmettre les fichiers
*/
private static final String OPTIONS_PARAM_NAME = "paramName";
/**
* nom de l'option qui indique la callback appelée sur le change
*/
private static final String OPTIONS_CHANGE = "change";
/**
* nom de l'option qui indique la méthode wicket qui communique avec le serveur
* pattern -> change -> fileuploadglue -> onChangeCallback
*/
private static final String OPTIONS_ON_CHANGE_CALLBACK = "onChangeCallback";
/**
* nom de l'option qui indique la méthode wicket qui communique avec le serveur
* pattern -> change -> fileuploadglue -> onUploadDoneCallback
*/
private static final String OPTIONS_ON_UPLOAD_DONE_CALLBACK = "onUploadDoneCallback";
/**
* nom de l'option qui indique la méthode wicket qui communique avec le serveur
* pattern -> change -> fileuploadglue -> onUploadFailsCallback
*/
private static final String OPTIONS_ON_UPLOAD_FAILS_CALLBACK = "onUploadFailsCallback";
/**
* nom de l'option pour l'auto-upload (désactivé dans notre cas)
*/
private static final String OPTIONS_AUTO_UPLOAD = "autoUpload";
/**
* nom de l'option pour la callback de progrès de l'upload
*/
private static final String OPTIONS_PROGRESSALL = "progressall";
/**
* nom de l'option de l'url pour l'upload des fichiers
*/
private static final String OPTIONS_URL = "url";
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final Component progressComponent;
public FileUploadBehavior(Component progressComponent) {
super();
this.progressComponent = progressComponent;
}
protected abstract String getFileUploadUrl();
/**
* File change callback. Override to perform custom tasks on file change.
*/
protected abstract List<FileApiFile> onFileChange(AjaxRequestTarget target, List<FileApiFile> fileList);
/**
* Override to perform custom tasks on error.
*
* @param mode callback mode being performed when error occurs.
* @param target
* @param exception
*/
protected void onError(FileUploadMode mode, AjaxRequestTarget target, Exception exception) {
}
/**
* Override to perform custom logging on Unknown mode error.
*
* @param exception
*/
protected void logUnknownMode(Exception exception) {
IRequestParameters req = RequestCycle.get().getRequest().getRequestParameters();
if (!req.getParameterValue(PARAMETERS_MODE).isEmpty()) {
LOGGER.error("Error reading mode ; {}", req.getParameterValue(PARAMETERS_MODE).toString(), exception);
} else {
LOGGER.error("Error reading mode ; empty parameter.", exception);
}
}
/**
* Override to perform custom logging on file list decoding error.
*
* @param exception
*/
protected void logReadFileListParameterError(Exception exception) {
IRequestParameters req = RequestCycle.get().getRequest().getRequestParameters();
if (!req.getParameterValue(PARAMETERS_FILE_LIST).isEmpty()) {
LOGGER.error("Error reading {} parameter ; {}", PARAMETERS_FILE_LIST, req.getParameterValue(PARAMETERS_FILE_LIST).toString(), exception);
} else {
LOGGER.error("Error reading {} parameter ; empty parameter.", PARAMETERS_FILE_LIST, exception);
}
}
/**
* wicket callback - performs method dispatching based on {@link FileUploadBehavior#PARAMETERS_MODE} parameter.
*
* @param target
*/
@Override
protected void respond(AjaxRequestTarget target) {
IRequestParameters req = RequestCycle.get().getRequest().getRequestParameters();
FileUploadMode mode;
try {
mode = req.getParameterValue(PARAMETERS_MODE).toEnum(FileUploadMode.class);
} catch (Exception e) {
// null or unknown value
logUnknownMode(e);
return;
}
switch (mode) {
case CHANGE:
respondChange(target);
break;
case UPLOAD_DONE:
respondUploadDone(target);
break;
case UPLOAD_FAILS:
respondUploadFails(target);
break;
default:
throw new IllegalStateException();
}
}
/**
* change wicket callback.
*
* @param target
*/
protected void respondUploadDone(AjaxRequestTarget target) {
IRequestParameters req = RequestCycle.get().getRequest().getRequestParameters();
try {
List<FileApiFile> successfileList = readSuccessFileApiFiles(req);
List<FileApiFile> errorfileList = readErrorFileApiFiles(req);
onFileUploadDone(target, successfileList, errorfileList);
} catch (Exception e) {
logReadFileListParameterError(e);
onError(FileUploadMode.UPLOAD_DONE, target, e);
return;
}
}
/**
* change wicket callback.
*
* @param target
*/
protected void respondUploadFails(AjaxRequestTarget target) {
IRequestParameters req = RequestCycle.get().getRequest().getRequestParameters();
try {
String errorMessage = req.getParameterValue(PARAMETERS_UPLOAD_FAILS_ERROR_MESSAGE).toString("");
FileUploadFailType failType = FileUploadFailType.fromName(req.getParameterValue(PARAMETERS_FAIL_TYPE).toString(""));
onFileUploadFails(target, failType, errorMessage);
} catch (Exception e) {
logReadFileListParameterError(e);
onError(FileUploadMode.UPLOAD_FAILS, target, e);
return;
}
}
protected abstract void onFileUploadDone(AjaxRequestTarget target, List<FileApiFile> successFileList, List<FileApiFile> errorFileList);
protected abstract void onFileUploadFails(AjaxRequestTarget target, FileUploadFailType failType, String errorMessage);
/**
* change wicket callback.
*
* @param target
* @return to upload file list (upload will be processed by objectUrl comparison)
*/
protected void respondChange(AjaxRequestTarget target) {
IRequestParameters req = RequestCycle.get().getRequest().getRequestParameters();
try {
List<FileApiFile> fileList = readFileApiFiles(req);
List<FileApiFile> acceptedFiles = onFileChange(target, fileList);
target.prependJavaScript("window." + req.getParameterValue(PARAMETERS_DATA_VARIABLE_NAME).toString() + " = JSON.parse(" + JsUtils.doubleQuotes(writeFileApiFiles(acceptedFiles), true) + "); if (console) { console.log(window." + req.getParameterValue(PARAMETERS_DATA_VARIABLE_NAME).toString() + "); }");
} catch (Exception e) {
logReadFileListParameterError(e);
onError(FileUploadMode.CHANGE, target, e);
return;
}
}
/**
* Appel des callbacks onSuccess / onFailure
*/
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
AjaxCallListener ajaxCallListener = new AjaxCallListener();
ajaxCallListener.onFailure("onFailure && onFailure(attrs, jqXHR, errorMessage, textStatus);");
ajaxCallListener.onSuccess("onSuccess && onSuccess(attrs, jqXHR, data, textStatus);");
attributes.getAjaxCallListeners().add(ajaxCallListener);
}
/**
* wicket javascript callback glue - function (fileList, onSuccess, onFailure) ; mode = 'change'
*/
private CharSequence getOnChangeCallbackScript() {
CallbackParameter callbackParameterMode = CallbackParameter.resolved(PARAMETERS_MODE, JsUtils.quotes(FileUploadMode.CHANGE.name()));
// installation dans le scope des callbacks d'échec et de succès
// réutilisés par les IAjaxCallListener
CallbackParameter callbackParameterFileList = CallbackParameter.explicit(PARAMETERS_FILE_LIST);
CallbackParameter callbackParameterDataVariableName = CallbackParameter.explicit(PARAMETERS_DATA_VARIABLE_NAME);
CallbackParameter callbackParameterOnSuccess = CallbackParameter.context("onSuccess");
CallbackParameter callbackParameterOnFailure = CallbackParameter.context("onFailure");
return getCallbackFunction(callbackParameterMode, callbackParameterDataVariableName, callbackParameterFileList, callbackParameterOnSuccess, callbackParameterOnFailure);
}
/**
* wicket javascript callback glue - function (fileList, onSuccess, onFailure) ; mode = 'change'
*/
private CharSequence getOnUploadFailsCallbackScript() {
CallbackParameter callbackParameterMode = CallbackParameter.resolved(PARAMETERS_MODE, JsUtils.quotes(FileUploadMode.UPLOAD_FAILS.name()));
// installation dans le scope des callbacks d'échec et de succès
// réutilisés par les IAjaxCallListener
CallbackParameter callbackParameterUploadFailType = CallbackParameter.explicit(PARAMETERS_FAIL_TYPE);
CallbackParameter callbackParameterErrorMessage = CallbackParameter.explicit(PARAMETERS_UPLOAD_FAILS_ERROR_MESSAGE);
CallbackParameter callbackParameterDataVariableName = CallbackParameter.explicit(PARAMETERS_DATA_VARIABLE_NAME);
CallbackParameter callbackParameterOnSuccess = CallbackParameter.context("onSuccess");
CallbackParameter callbackParameterOnFailure = CallbackParameter.context("onFailure");
return getCallbackFunction(callbackParameterMode, // mode
callbackParameterDataVariableName, // variable d'échange
callbackParameterUploadFailType, // type d'échec
callbackParameterErrorMessage, // message d'erreur
callbackParameterOnSuccess, callbackParameterOnFailure);
}
/**
* wicket javascript callback glue - function (fileList, onSuccess, onFailure) ; mode = 'change'
*/
private CharSequence getOnUploadDoneCallbackScript() {
CallbackParameter callbackParameterMode = CallbackParameter.resolved(PARAMETERS_MODE, JsUtils.quotes(FileUploadMode.UPLOAD_DONE.name()));
// installation dans le scope des callbacks d'échec et de succès
// réutilisés par les IAjaxCallListener
CallbackParameter callbackParameterSuccessFileListName = CallbackParameter.explicit(PARAMETERS_UPLOAD_DONE_SUCCESS_FILE_LIST);
CallbackParameter callbackParameterErrorFileListName = CallbackParameter.explicit(PARAMETERS_UPLOAD_DONE_ERROR_FILE_LIST);
CallbackParameter callbackParameterDataVariableName = CallbackParameter.explicit(PARAMETERS_DATA_VARIABLE_NAME);
CallbackParameter callbackParameterOnSuccess = CallbackParameter.context("onSuccess");
CallbackParameter callbackParameterOnFailure = CallbackParameter.context("onFailure");
return getCallbackFunction(callbackParameterMode, // mode
callbackParameterDataVariableName, // variable d'échange
callbackParameterSuccessFileListName, callbackParameterErrorFileListName, // listes des succès et échecs
callbackParameterOnSuccess, callbackParameterOnFailure);
}
/**
* widget initialization code generation.
*
* @param component
*/
protected JsStatement statement(Component component) {
Options options = new Options();
// javascript callback method called on change event
options.put(OPTIONS_CHANGE, new JsScopeFunction("$.fileuploadglue.onChange"));
// options used by change callback to communicate with wicket
options.put(OPTIONS_ON_CHANGE_CALLBACK, getOnChangeCallbackScript().toString());
options.put(OPTIONS_ON_UPLOAD_FAILS_CALLBACK, getOnUploadFailsCallbackScript().toString());
options.put(OPTIONS_ON_UPLOAD_DONE_CALLBACK, getOnUploadDoneCallbackScript().toString());
options.put(OPTIONS_AUTO_UPLOAD, false);
options.put(OPTIONS_URL, new LiteralOption(getFileUploadUrl(), true).toString());
options.put(OPTIONS_PARAM_NAME, new LiteralOption(PARAMETERS_FILE_UPLOAD, true).toString());
// la fonction génère une fonction de callback ciblant le progresscomponent indiqué
options.put(OPTIONS_PROGRESSALL, new JsStatement().$().chain("fileuploadglue.progressallCallbackGenerator", JsUtils.doubleQuotes(progressComponent.getMarkupId(), true)).render(false).toString());
return new JsStatement().$(component).chain("fileupload", options.getJavaScriptOptions());
}
/**
* Component head participation.
*/
@Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(JavaScriptHeaderItem.forReference(FileUploadGlueJavaScriptResourceReference.get()));
response.render(OnDomReadyHeaderItem.forScript(statement(component).render()));
}
public static List<FileApiFile> readSuccessFileApiFiles(IRequestParameters parameters) throws JsonProcessingException, IOException {
return OBJECT_MAPPER.readerFor(OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, FileApiFile.class)).readValue(parameters.getParameterValue(PARAMETERS_UPLOAD_DONE_SUCCESS_FILE_LIST).toString());
}
public static List<FileApiFile> readErrorFileApiFiles(IRequestParameters parameters) throws JsonProcessingException, IOException {
return OBJECT_MAPPER.readerFor(OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, FileApiFile.class)).readValue(parameters.getParameterValue(PARAMETERS_UPLOAD_DONE_ERROR_FILE_LIST).toString());
}
public static List<FileApiFile> readFileApiFiles(IRequestParameters parameters) throws JsonProcessingException, IOException {
return OBJECT_MAPPER.readerFor(OBJECT_MAPPER.getTypeFactory().constructParametrizedType(List.class, List.class, FileApiFile.class)).readValue(parameters.getParameterValue(PARAMETERS_FILE_LIST).toString());
}
public static String writeFileApiFiles(List<FileApiFile> files) throws JsonProcessingException, IOException {
return OBJECT_MAPPER.writeValueAsString(files);
}
}