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:bs@nuxeo.com">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 (pageId == WizardPage.NEXT_PAGE) {
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}