package fr.openwide.core.wicket.more.export; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import java.io.File; import org.apache.wicket.Component; import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.Behavior; import org.apache.wicket.behavior.IBehaviorListener; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.OnDomReadyHeaderItem; import org.apache.wicket.model.IModel; import org.apache.wicket.request.Url; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.ContentDisposition; import org.apache.wicket.util.lang.Classes; import org.apache.wicket.util.resource.FileResourceStream; import org.apache.wicket.util.resource.IResourceStream; import org.wicketstuff.wiquery.core.javascript.JsStatement; import org.wicketstuff.wiquery.core.javascript.JsUtils; import fr.openwide.core.wicket.more.util.model.Detachables; /** * @author Sven Meier * @author Ernesto Reinaldo Barreiro (reiern70@gmail.com) * @author Jordi Deu-Pons (jordi@jordeu.net) * @author Renaud Joly * @author Florian Lacreuse * @author Yoann Rodière * @see <a href="https://cwiki.apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow">AJAX update and file download in one blow</a> */ public abstract class AbstractDeferredDownloadBehavior extends Behavior { private static final long serialVersionUID = -4484163101766083913L; private Component component; protected final IModel<File> tempFileModel; private final boolean addAntiCache; private final ResourceDownloadBehavior resourceDownloadBehavior; public AbstractDeferredDownloadBehavior(IModel<File> tempFileModel) { this(tempFileModel, true); } public AbstractDeferredDownloadBehavior(IModel<File> tempFileModel, boolean addAntiCache) { super(); this.tempFileModel = checkNotNull(tempFileModel); this.addAntiCache = addAntiCache; this.resourceDownloadBehavior = new ResourceDownloadBehavior(); } private class ResourceDownloadBehavior extends Behavior implements IBehaviorListener { private static final long serialVersionUID = -7831306343846345227L; @Override public void onRequest() { ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(getResourceStream(), getFileDisplayName()); handler.setContentDisposition(ContentDisposition.ATTACHMENT); getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler); } } @Override public void onConfigure(Component component) { super.onConfigure(component); Page page = component.getPage(); if (!page.getBehaviors().contains(resourceDownloadBehavior)) { page.add(resourceDownloadBehavior); } } @Override public final void bind(Component component) { checkState(this.component == null, "Cannot add a " + Classes.simpleName(getClass()) + " to multiple components"); this.component = checkNotNull(component); } @Override public final void unbind(Component component) { if (this.component == component) { this.component = null; } } public Component getComponent() { checkState(this.component != null, "A " + Classes.simpleName(getClass()) + " must be added to a component before use"); return component; } /** * Call this method to initiate the download after an ajax refresh. */ public final void initiate(AjaxRequestTarget target) { target.appendJavaScript(createDownloadJsStatement(false).render()); } private String getUrl(boolean fullUrl) { String url = getComponent().getPage().urlFor(resourceDownloadBehavior, IBehaviorListener.INTERFACE, new PageParameters()).toString(); if (fullUrl) { url = RequestCycle.get().getUrlRenderer() .renderFullUrl( Url.parse(url) ); } if (addAntiCache) { url = url + (url.contains("?") ? "&" : "?"); url = url + "t=" + System.currentTimeMillis(); } return url; } public final String getFullUrl() { return getUrl(true); } private JsStatement createDownloadJsStatement(boolean fullUrl) { String url = getUrl(fullUrl); // the timeout is needed to let Wicket release the channel return new JsStatement().append("window").chain("setTimeout", JsUtils.doubleQuotes("window.location.href=" + JsUtils.quotes(url, true), true), String.valueOf(100)); } /** * Call this method to redirect to a page ({@link Component#setResponsePage(Page)}) and then initiate the download. */ public final void initiate(Page redirectPage) { final CharSequence statement = createDownloadJsStatement(true).render(); redirectPage.add(new DoOnDomReadyBehavior(statement)); getComponent().setResponsePage(redirectPage); } private static class DoOnDomReadyBehavior extends Behavior { private static final long serialVersionUID = 1L; private final CharSequence statement; public DoOnDomReadyBehavior(CharSequence statement) { super(); this.statement = statement; } @Override public boolean isTemporary(Component component) { return true; } @Override public void renderHead(Component component, IHeaderResponse response) { response.render(OnDomReadyHeaderItem.forScript(statement)); } } protected abstract String getFileDisplayName(); protected IResourceStream getResourceStream() { File file = tempFileModel.getObject(); if (file != null && file.exists() && file.canRead() && file.isFile()) { return new FileResourceStream(file); } throw new IllegalStateException(); } @Override public void detach(Component component) { super.detach(component); Detachables.detach(tempFileModel); } }