001    /*
002     * Databinder: a simple bridge from Wicket to Hibernate
003     * Copyright (C) 2006  Nathan Hamblen nathan@technically.us
004    
005     * This library is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public
007     * License as published by the Free Software Foundation; either
008     * version 2.1 of the License, or (at your option) any later version.
009     *
010     * This library is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * Lesser General Public License for more details.
014     *
015     * You should have received a copy of the GNU Lesser General Public
016     * License along with this library; if not, write to the Free Software
017     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
018     */
019    
020    package net.databinder.components.hib;
021    
022    import java.io.Serializable;
023    
024    import net.databinder.models.hib.HibernateObjectModel;
025    
026    import org.apache.wicket.Component;
027    import org.apache.wicket.WicketRuntimeException;
028    import org.apache.wicket.markup.html.link.Link;
029    import org.apache.wicket.model.CompoundPropertyModel;
030    import org.apache.wicket.model.IModel;
031    import org.hibernate.Session;
032    
033    /**
034     * Form for a persistent model object nested in a BoundCompoundPropertyModel.
035     * Saves the model object to persistent storage when a valid form is submitted. This
036     * form can be a child component of any Wicket page.
037     * @author Nathan Hamblen
038     */
039    public class DataForm extends DataFormBase {
040            private Serializable version;
041    
042            /**
043             * Instantiates this form and a new, blank instance of the given class as a persistent model
044             * object. By default the model object created is serialized and retained between requests until
045             * it is persisted.
046             * @param id
047             * @param modelClass for the persistent object
048             * @see HibernateObjectModel#setRetainUnsaved(boolean)
049             */
050            public DataForm(String id, Class modelClass) {
051                    super(id, new CompoundPropertyModel(new HibernateObjectModel(modelClass)));
052            }
053    
054            public DataForm(String id, HibernateObjectModel model) {
055                    super(id, new CompoundPropertyModel(model));
056                    setFactoryKey(model.getFactoryKey());
057            }
058    
059            /**
060             * Instantiates this form with a persistent object of the given class and id.
061             * @param id Wicket id
062             * @param modelClass for the persistent object
063             * @param persistentObjectId id of the persistent object
064             */
065            public DataForm(String id, Class modelClass, Serializable persistentObjectId) {
066                    super(id, new CompoundPropertyModel(new HibernateObjectModel(modelClass, persistentObjectId)));
067            }
068    
069            /**
070             * Form that is nested below a component with a compound model containing a Hibernate
071             * model.
072             * @param id
073             */
074            public DataForm(String id) {
075                    super(id);
076            }
077            
078            /**
079             * @param key for the Hibernate session factory to be used with this component
080             * @return this 
081             */
082            @Override
083            public DataForm setFactoryKey(Object key) {
084                    super.setFactoryKey(key);
085                    getPersistentObjectModel().setFactoryKey(key);
086                    return this;
087            }
088    
089            public HibernateObjectModel getPersistentObjectModel() {
090                    return (HibernateObjectModel) getCompoundModel().getChainedModel();
091            }
092    
093            /**
094             * Change the persistent model object of this form.
095             * @param object  to attach to this form
096             * @return this form, for chaining
097             */
098            public DataForm setPersistentObject(Object object) {
099                    getPersistentObjectModel().setObject(object);
100                    modelChanged();
101                    return this;
102            }
103            
104            private void updateVersion() {
105                    version = getPersistentObjectModel().getVersion();
106            }
107    
108            /** Late-init version record. */
109            @Override
110            protected void onBeforeRender() {
111                    super.onBeforeRender();
112                    if (version == null)
113                            updateVersion();
114            }
115            
116            @Override
117            protected void onModelChanged() {
118                    updateVersion();
119            }
120    
121            /**
122             * Replaces the form's model object with a new, blank instance. Does not affect
123             * persistent storage.
124             * @return this form, for chaining
125             */
126            public DataForm clearPersistentObject() {
127                    getPersistentObjectModel().unbind();
128                    modelChanged();
129                    return this;
130            }
131    
132            protected CompoundPropertyModel getCompoundModel() {
133                    IModel model = getModel();
134                    Component cur = this;
135                    while (cur != null) {
136                            model = cur.getModel();
137                            if (model != null && model instanceof CompoundPropertyModel)
138                                    return (CompoundPropertyModel) model;
139                            cur = cur.getParent();
140                    }
141                    throw new WicketRuntimeException("DataForm has no parent compound model");
142            }
143    
144            /**
145             * Saves the form's model object to persistent storage if it is new and commits
146             * the database transaction.
147             */
148            @Override
149            protected void onSubmit() {
150                    Object modelObject = getPersistentObjectModel().getObject();
151                    Session session = getHibernateSession();
152                    if (!session.contains(modelObject)) {
153                            session.save(modelObject);
154                            // updating binding status; though it will happen on detach
155                            // some UI components may like to know sooner.
156                            getPersistentObjectModel().checkBinding();
157                    }
158                    super.onSubmit();       // flush and commit session
159                    // if version is present it should have changed
160                    if (version != null) {
161                            updateVersion();
162                    }
163            }
164    
165            /**
166             * Checks that the version number, if present, is the last known version number.
167             * If it does not match, validation fails and will continue to fail until the form is
168             * reloaded with the updated data and version number. This allows the user to
169             * preserve her unsaved changes while preventing overwrites. <p> <b>Note:</b> although
170             * timestamp versions are supported, beware of rounding errors. equals() must return true
171             * when comparing the retained version object to the one loaded from persistent storage.
172             */
173            @Override
174            protected void validate() {
175                    if (version != null) {
176                            Serializable currentVersion = getPersistentObjectModel().getVersion();
177                            if (!version.equals(currentVersion))
178                                    error(getString("version.mismatch", null)); // report error
179                                    // do not update version number as old data still appears in form
180                    }
181    
182                    super.validate();
183            }
184    
185            /**
186             * @return persistent storage version number if available, null otherwise
187             */
188            protected Serializable getVersion() {
189                    return version;
190            }
191    
192            /**
193             * Deletes the form's model object from persistent storage. Flushes change so that
194             * queries executed in the same request (e.g., in a HibernateListModel) will not return
195             * this object.
196             * @return true if the object was deleted, false if it did not exist
197             */
198            protected boolean deletePersistentObject() {
199                    Session session = getHibernateSession();
200                    Object modelObject = getPersistentObjectModel().getObject();
201                    if (!session.contains(modelObject))
202                            return false;
203                    session.delete(modelObject);
204                    session.flush();
205                    return true;
206            }
207            
208            public class ClearLink extends Link {
209                    public ClearLink(String id) {
210                            super(id);
211                    }
212                    @Override
213                    public boolean isEnabled() {
214                            return !DataForm.this.isVisibleInHierarchy() || getPersistentObjectModel().isBound();
215                    }
216                    @Override
217                    public void onClick() {
218                            clearPersistentObject();
219                            DataForm.this.setVisible(true);
220                    }
221            }
222    }