001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * bstefanescu 016 */ 017package org.nuxeo.ecm.webengine.ui.wizard; 018 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Map; 023 024import javax.servlet.http.HttpSession; 025import javax.ws.rs.GET; 026import javax.ws.rs.POST; 027import javax.ws.rs.Path; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.ecm.webengine.forms.validation.Form; 032import org.nuxeo.ecm.webengine.forms.validation.ValidationException; 033import org.nuxeo.ecm.webengine.model.impl.DefaultObject; 034 035/** 036 * The following actions are available: 037 * <ul> 038 * <li>GET 039 * <li>POST next 040 * <li>POST ok 041 * <li>POST cancel 042 * <li>POST back 043 * </ul> 044 * 045 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 046 */ 047public abstract class Wizard extends DefaultObject { 048 049 private static final Log log = LogFactory.getLog(Wizard.class); 050 051 public static final String[] EMPTY = new String[0]; 052 053 protected WizardSession session; 054 055 protected WizardPage page; // current wizard page 056 057 protected ValidationException error; 058 059 protected Map<String, String[]> initialFields; 060 061 protected abstract WizardPage[] createPages(); 062 063 protected Map<String, String[]> createInitialFields() { 064 return null; 065 } 066 067 @Override 068 protected void initialize(Object... args) { 069 super.initialize(args); 070 HttpSession httpSession = ctx.getRequest().getSession(true); 071 String key = createSessionId(); 072 session = (WizardSession) httpSession.getAttribute(key); 073 if (session == null) { 074 session = new WizardSession(key, createPages()); 075 httpSession.setAttribute(key, session); 076 initialFields = createInitialFields(); 077 if (initialFields == null) { 078 initialFields = new HashMap<String, String[]>(); 079 } 080 } 081 page = (WizardPage) session.getPage(); // the current page 082 } 083 084 protected void destroySession() { 085 HttpSession httpSession = ctx.getRequest().getSession(false); 086 if (httpSession != null) { 087 httpSession.removeAttribute(session.getId()); 088 } 089 } 090 091 protected String createSessionId() { 092 return "wizard:" + getClass(); 093 } 094 095 public WizardSession getSession() { 096 return session; 097 } 098 099 public WizardPage getPage() { 100 return page; 101 } 102 103 public boolean isNextEnabled() { 104 return page.isNextEnabled(); 105 } 106 107 public boolean isBackEnabled() { 108 return page.isBackEnabled(); 109 } 110 111 public boolean isOkEnabled() { 112 return page.isOkEnabled(); 113 } 114 115 public boolean isCancelEnabled() { 116 return page.isCancelEnabled(); 117 } 118 119 public ValidationException getError() { 120 return error; 121 } 122 123 @SuppressWarnings("unchecked") 124 public Map<String, String[]> getFormFields() { 125 Form form = session.getPage().getForm(); 126 if (form != null) { 127 return form.fields(); 128 } 129 return initialFields == null ? Collections.EMPTY_MAP : initialFields; 130 } 131 132 public String getField(String key) { 133 String[] v = getFormFields().get(key); 134 return v != null && v.length > 0 ? v[0] : null; 135 } 136 137 public String[] getFields(String key) { 138 String[] fields = getFormFields().get(key); 139 return fields == null ? EMPTY : fields; 140 } 141 142 public Collection<String> getInvalidFields() { 143 if (error != null) { 144 return error.getInvalidFields(); 145 } 146 return null; 147 } 148 149 public Collection<String> getRequireddFields() { 150 if (error != null) { 151 return error.getRequiredFields(); 152 } 153 return null; 154 } 155 156 public boolean hasErrors() { 157 return error != null; 158 } 159 160 public boolean hasErrors(String key) { 161 if (error != null) { 162 return error.hasErrors(key); 163 } 164 return false; 165 } 166 167 protected Object redirectOnOk() { 168 return redirect(getPrevious().getPath()); 169 } 170 171 protected Object redirectOnCancel() { 172 return redirect(getPrevious().getPath()); 173 } 174 175 public <T extends Form> T getForm(Class<T> formType) { 176 return session.getForm(formType); 177 } 178 179 protected abstract void performOk() throws ValidationException; 180 181 protected void performCancel() { 182 destroySession(); 183 } 184 185 protected Object handleValidationError(ValidationException e) { 186 // set the error and redisplay the current page 187 session.setError(e); 188 return redirect(getPath()); 189 } 190 191 protected Object handleError(Throwable e) { 192 // set the error and redisplay the current page 193 log.error("Processing failed in wizard page: " + session.getPage().getId(), e); 194 session.setError(new ValidationException("Processing failed: " + e.getMessage(), e)); 195 return redirect(getPath()); 196 } 197 198 @SuppressWarnings("unchecked") 199 public <T extends Form> T validate(WizardPage page) throws ValidationException { 200 try { 201 Form form = ctx.getForm().validate(page.getFormType()); 202 page.setForm(form); 203 return (T) form; 204 } catch (ValidationException e) { 205 page.setForm(e.getForm()); 206 throw e; 207 } 208 } 209 210 @POST 211 @Path("next") 212 public Object handleNext() { 213 String pageId = null; 214 try { 215 // process page 216 pageId = page.getNextPage(this, validate(page)); 217 if (pageId == WizardPage.NEXT_PAGE) { 218 pageId = session.getPageAt(page.getIndex() + 1); 219 } 220 if (pageId == null) { // finish the wizard 221 performOk(); 222 destroySession(); 223 return redirectOnOk(); 224 } else { // go to the next page 225 session.pushPage(pageId); 226 return redirect(getPath()); 227 } 228 } catch (ValidationException e) { 229 return handleValidationError(e); 230 } catch (RuntimeException e) { 231 return handleError(e); 232 } 233 } 234 235 @POST 236 @Path("back") 237 public Object handleBack() { 238 session.popPage(); // go to previous page 239 return redirect(getPath()); 240 } 241 242 @POST 243 @Path("cancel") 244 public Object handleCancel() { 245 performCancel(); 246 return redirectOnCancel(); 247 } 248 249 @POST 250 @Path("ok") 251 public Object handleOk() { 252 try { 253 validate(page);// don't matter if there is a next page 254 performOk(); 255 destroySession(); 256 return redirectOnOk(); 257 } catch (ValidationException e) { 258 return handleValidationError(e); 259 } catch (RuntimeException e) { 260 return handleError(e); 261 } 262 } 263 264 /** 265 * Get the content of the current wizard page. 266 */ 267 @GET 268 public Object doGet() { 269 error = session.removeError(); 270 return getView(page.getId()); 271 } 272 273}