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