View Javadoc

1   package org.appfuse.webapp.client.application.base.activity;
2   
3   import java.util.Set;
4   import java.util.logging.Level;
5   import java.util.logging.Logger;
6   
7   import javax.validation.ConstraintViolation;
8   
9   import org.appfuse.webapp.client.application.Application;
10  import org.appfuse.webapp.client.application.base.place.EntityProxyPlace;
11  import org.appfuse.webapp.client.application.base.place.EntitySearchPlace;
12  import org.appfuse.webapp.client.application.base.view.ProxyEditView;
13  import org.appfuse.webapp.client.ui.home.HomePlace;
14  
15  import com.github.gwtbootstrap.client.ui.constants.AlertType;
16  import com.google.gwt.activity.shared.Activity;
17  import com.google.gwt.event.shared.EventBus;
18  import com.google.gwt.place.shared.Place;
19  import com.google.gwt.user.client.Window;
20  import com.google.gwt.user.client.ui.AcceptsOneWidget;
21  import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
22  import com.google.web.bindery.requestfactory.shared.EntityProxy;
23  import com.google.web.bindery.requestfactory.shared.EntityProxyId;
24  import com.google.web.bindery.requestfactory.shared.Receiver;
25  import com.google.web.bindery.requestfactory.shared.Request;
26  import com.google.web.bindery.requestfactory.shared.RequestContext;
27  import com.google.web.bindery.requestfactory.shared.ServerFailure;
28  
29  /**
30   * Abstract activity for editing a record. Subclasses must provide access to the
31   * request that will be fired when Save is clicked.
32   * <p>
33   * Instances are not reusable. Once an activity is stoped, it cannot be
34   * restarted.
35   * 
36   * Required methods are:
37   * <ol>
38   * <li> {@link #createView(Place)}</li>
39   * <li> {@link #createProxyRequest()}</li>
40   * <li> {@link #loadProxyRequest(RequestContext, EntityProxyId)}</li>
41   * <li> {@link #saveOrUpdateRequest(RequestContext, EntityProxy)}</li>
42   * <li> {@link #deleteRequest(RequestContext, EntityProxy)}</li>
43   * </ol>
44   * 
45   * Customization:
46   * <ol>
47   * <li> {@link #createProxy(RequestContext)}</li>
48   * <li> {@link #getEntityId()}</li>
49   * <li> {@link #getProxyClass()}</li>
50   * <li> {@link #loadProxyRequest(RequestContext, EntityProxyId)}</li>
51   * <li> {@link #getSavedMessage()}</li>
52   * <li> {@link #getDeletedMessage()}</li>
53   * <li> {@link #nextPlace(boolean)}</li>
54   * <li> {@link #previousPlace()}</li>
55   * </ol>
56   * 
57   * @param <P>
58   *            the type of Proxy being edited
59   */
60  public abstract class AbstractProxyEditActivity<P extends EntityProxy> extends AbstractBaseActivity implements Activity, ProxyEditView.Delegate {
61  
62      protected ProxyEditView<P, ?> view;
63      protected RequestFactoryEditorDriver<P, ?> editorDriver;
64      protected P entityProxy;
65      private boolean waiting;
66  
67      private String abandonChangesMessage = "Are you sure you want to abandon your changes?";// FIXME
68                                                                                              // i18n
69      private String savedMessage = i18n.entity_saved();
70      private String deletedMessage = i18n.entity_deleted();
71      private String deleteConfirmation = i18n.delete_confirm("");
72  
73      public String getAbandonChangesMessage() {
74          return abandonChangesMessage;
75      }
76  
77      public void setAbandonChangesMessage(final String abandonChangesMessage) {
78          this.abandonChangesMessage = abandonChangesMessage;
79      }
80  
81      public String getSavedMessage() {
82          return savedMessage;
83      }
84  
85      public void setSavedMessage(final String savedMessage) {
86          this.savedMessage = savedMessage;
87      }
88  
89      public String getDeletedMessage() {
90          return deletedMessage;
91      }
92  
93      public void setDeletedMessage(final String deletedMessage) {
94          this.deletedMessage = deletedMessage;
95      }
96  
97      public String getDeleteConfirmation() {
98          return deleteConfirmation;
99      }
100 
101     public void setDeleteConfirmation(final String deleteConfirmation) {
102         this.deleteConfirmation = deleteConfirmation;
103     }
104 
105     /**
106      * Called once to create the appropriate request to create/find and edit
107      * this entity and that will be fired when the save button is clicked.
108      * 
109      * @return the request context to edit this entity proxy.
110      */
111     protected abstract RequestContext createProxyRequest();
112 
113     /**
114      * 
115      * @param requestContext
116      * @return
117      */
118     protected P createProxy(final RequestContext requestContext) {
119         return (P) requestContext.create(getProxyClass());
120     }
121 
122     /**
123      * Create are Request to load this entity proxy.
124      * 
125      * Note: Request.find() method is disabled in the server for security
126      * reasons.
127      * 
128      * @param requestContext
129      * @param proxyId
130      * @return
131      */
132     protected abstract Request<P> loadProxyRequest(RequestContext requestContext, String proxyId);
133 
134     /**
135      * 
136      * @param requestContext
137      * @param proxy
138      * @return
139      */
140     protected abstract RequestContext saveOrUpdateRequest(RequestContext requestContext, P proxy);
141 
142     /**
143      * Called on {@link #deleteClicked()} to create the appropriate request to
144      * delete entity.
145      * 
146      * @return the request context to fire when the delete button is clicked
147      */
148     protected abstract RequestContext deleteRequest(RequestContext requestContext, P proxy);
149 
150     /**
151      * @param currentPlace
152      * @param view
153      */
154     public AbstractProxyEditActivity(final Application application, final ProxyEditView<P, ?> view) {
155         super(application);
156         this.view = view;
157     }
158 
159     /**
160      * 
161      */
162     @Override
163     public void start(final AcceptsOneWidget display, final EventBus eventBus) {
164         view.setDelegate(this);
165         editorDriver = view.createEditorDriver();
166 
167         doLoadEntityProxy(new Receiver<P>() {
168             @Override
169             public void onSuccess(final P response) {
170                 entityProxy = response;
171                 display.setWidget(view);
172                 setDocumentTitleAndBodyAttributtes();
173             }
174         });
175     }
176 
177     protected String getEntityId() {
178         if (currentPlace instanceof EntityProxyPlace) {
179             return ((EntityProxyPlace) currentPlace).getEntityId();
180         }
181         return null;
182     }
183 
184     protected Class<? extends EntityProxy> getProxyClass() {
185         if (currentPlace instanceof EntityProxyPlace) {
186             return ((EntityProxyPlace) currentPlace).getProxyClass();
187         }
188         return null;
189     }
190 
191     /**
192      * 
193      */
194     protected void doLoadEntityProxy(final Receiver<P> onloadCallback) {
195         final String proxyId = getEntityId();
196         if (proxyId == null) {
197             // create a brand new proxy entity
198             final RequestContext requestContext = createProxyRequest();
199             final P proxy = createProxy(requestContext);
200             // edit this entity proxy on the same request it was created
201             editorDriver.edit(proxy, saveOrUpdateRequest(requestContext, proxy));
202             // finish loading
203             onloadCallback.onSuccess(proxy);
204         } else {
205             // find this entity proxy on the server
206             loadProxyRequest(createProxyRequest(), proxyId)
207                     .with(editorDriver.getPaths())
208                     .fire(new Receiver<P>() {
209                         @Override
210                         public void onSuccess(final P response) {
211                             if (editorDriver != null) {
212                                 // edit this entity proxy on a new request
213                                 editorDriver.edit(response, saveOrUpdateRequest(createProxyRequest(), response));
214                                 // finish loading
215                                 onloadCallback.onSuccess(response);
216                             }
217                         }
218                     });
219         }
220     }
221 
222     /**
223      * 
224      * @see org.appfuse.webapp.client.application.base.view.ProxyEditView.Delegate#saveClicked()
225      */
226     @Override
227     public void saveClicked() {
228 
229         setWaiting(true);
230         editorDriver.flush().fire(new Receiver<Void>() {
231             /*
232              * Callbacks do nothing if editorDriver is null, we were stopped in
233              * midflight
234              */
235             @Override
236             public void onFailure(final ServerFailure error) {
237                 if (editorDriver != null) {
238                     setWaiting(false);
239                     throw new RuntimeException(error.getMessage());// FIXME
240                 }
241             }
242 
243             @Override
244             public void onSuccess(final Void ignore) {
245                 if (editorDriver != null) {
246                     editorDriver = null;
247                     setWaiting(false);
248                     placeController.goTo(nextPlace(true));
249                     addMessage(getSavedMessage(), AlertType.SUCCESS);
250                 }
251             }
252 
253             @Override
254             public void onConstraintViolation(final Set<ConstraintViolation<?>> violations) {
255                 if (editorDriver != null) {
256                     setWaiting(false);
257                     editorDriver.setConstraintViolations(violations);
258                 }
259             }
260 
261         });
262     }
263 
264     /**
265      * 
266      * @see org.appfuse.webapp.client.application.base.view.ProxyEditView.Delegate#deleteClicked()
267      */
268     @Override
269     public void deleteClicked() {
270         if (!Window.confirm(getDeleteConfirmation())) {
271             return;
272         }
273         deleteRequest(createProxyRequest(), entityProxy).fire(new Receiver<Void>() {
274             @Override
275             public void onSuccess(final Void response) {
276                 placeController.goTo(nextPlace(false));
277                 addMessage(getDeletedMessage(), AlertType.SUCCESS);
278             }
279         });
280     }
281 
282     /**
283      * 
284      * @see org.appfuse.webapp.client.application.base.view.ProxyEditView.Delegate#cancelClicked()
285      */
286     @Override
287     public void cancelClicked() {
288         final String unsavedChangesWarning = mayStop();
289         if ((unsavedChangesWarning == null)
290                 || Window.confirm(unsavedChangesWarning)) {
291             editorDriver = null;
292             placeController.goTo(previousPlace());
293         }
294     }
295 
296     /**
297      * Creates the {@link Place} to go when this activity is canceled.
298      * 
299      * @param saved
300      * @return
301      */
302     protected Place previousPlace() {
303         return new EntitySearchPlace(getProxyClass());
304     }
305 
306     /**
307      * Creates the {@link Place} to go after successfully saved or deleted this
308      * entity.
309      * 
310      * @param saved
311      * @return
312      */
313     protected Place nextPlace(final boolean saved) {
314         if (saved) {
315             return new EntitySearchPlace(getProxyClass());
316         } else { // deleted
317             return new HomePlace();
318         }
319     }
320 
321     protected void clearMessages() {
322         shell.clearMessages();
323     }
324 
325     protected void addMessage(final String html, final AlertType alertType) {
326         shell.addMessage(html, alertType);
327     }
328 
329     /**
330      * 
331      * @see com.google.gwt.activity.shared.AbstractActivity#mayStop()
332      */
333     @Override
334     public String mayStop() {
335         if (isWaiting() || changed()) {
336             return getAbandonChangesMessage();
337         }
338 
339         return null;
340     }
341 
342     /**
343      * 
344      * @see com.google.gwt.activity.shared.AbstractActivity#onCancel()
345      */
346     @Override
347     public void onCancel() {
348         onStop();
349     }
350 
351     /**
352      * 
353      * @see com.google.gwt.activity.shared.AbstractActivity#onStop()
354      */
355     @Override
356     public void onStop() {
357         view.setDelegate(null);
358         editorDriver = null;
359     }
360 
361     /**
362      * 
363      * @return
364      */
365     private boolean changed() {
366         try {
367             return editorDriver != null && editorDriver.isDirty();
368         } catch (final Exception e) {
369             Logger.getLogger("").log(Level.SEVERE, e.getMessage(), e);
370             return false;
371         }
372     }
373 
374     /**
375      * @return true if we're waiting for an rpc response.
376      */
377     protected boolean isWaiting() {
378         return waiting;
379     }
380 
381     /**
382      * While we are waiting for a response, we cannot poke setters on the proxy
383      * (that is, we cannot call editorDriver.flush). So we set the waiting flag
384      * to warn ourselves not to, and to disable the view.
385      */
386     protected void setWaiting(final boolean wait) {
387         this.waiting = wait;
388         view.setEnabled(!wait);
389     }
390 
391 }