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}