001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.model;
018
019import java.util.Locale;
020
021import org.danekja.java.util.function.serializable.SerializableSupplier;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025
026/**
027 * Model that makes working with detachable models a breeze. LoadableDetachableModel holds a
028 * temporary, transient model object, that is set when {@link #getObject()} is called by calling
029 * abstract method 'load', and that will be reset/ set to null on {@link #detach()}.
030 * 
031 * A usage example:
032 * 
033 * <pre>
034 * LoadableDetachableModel venueListModel = new LoadableDetachableModel()
035 * {
036 *      protected Object load()
037 *      {
038 *              return getVenueDao().findVenues();
039 *      }
040 * };
041 * </pre>
042 * 
043 * <p>
044 * Though you can override methods {@link #onAttach()} and {@link #onDetach()} for additional
045 * attach/ detach behavior, the point of this class is to hide as much of the attaching/ detaching
046 * as possible. So you should rarely need to override those methods, if ever.
047 * </p>
048 * 
049 * @author Eelco Hillenius
050 * @author Igor Vaynberg
051 * 
052 * @param <T>
053 *            The Model Object type
054 */
055public abstract class LoadableDetachableModel<T> implements IModel<T>
056{
057        private static final long serialVersionUID = 1L;
058
059        private static final Logger log = LoggerFactory.getLogger(LoadableDetachableModel.class);
060
061        /** Internal state of the LoadableDetachableModel. */
062        private enum InternalState {
063                DETACHED, ATTACHING, ATTACHED;
064
065                @Override
066                public String toString()
067                {
068                        return name().toLowerCase(Locale.ROOT);
069                }
070        }
071
072        /** Keeps track of whether this model is attached or detached */
073        private transient InternalState state = InternalState.DETACHED;
074
075        /** temporary, transient object. */
076        private transient T transientModelObject;
077
078        /**
079         * Default constructor, constructs the model in detached state with no data associated with the
080         * model.
081         */
082        public LoadableDetachableModel()
083        {
084        }
085
086        /**
087         * This constructor is used if you already have the object retrieved and want to wrap it with a
088         * detachable model. Constructs the model in attached state. Calls to {@link #getObject()} will
089         * return {@code object} until {@link #detach()} is called.
090         * 
091         * @param object
092         *            retrieved instance of the detachable object
093         */
094        public LoadableDetachableModel(T object)
095        {
096                this.transientModelObject = object;
097                state = InternalState.ATTACHED;
098        }
099
100        @Override
101        public void detach()
102        {
103                // even if LDM is in partial attached state (ATTACHING) it should be detached
104                if (state != null && state != InternalState.DETACHED)
105                {
106                        try
107                        {
108                                onDetach();
109                        }
110                        finally
111                        {
112                                state = InternalState.DETACHED;
113                                transientModelObject = null;
114
115                                log.debug("removed transient object for '{}'", this);
116                        }
117                }
118        }
119
120        @Override
121        public final T getObject()
122        {
123                if (state == null || state == InternalState.DETACHED)
124                {
125                        // prevent infinite attachment loops
126                        state = InternalState.ATTACHING;
127
128                        transientModelObject = load();
129
130                        if (log.isDebugEnabled())
131                        {
132                                log.debug("loaded transient object '{}' for '{}'", transientModelObject, this);
133                        }
134
135                        state = InternalState.ATTACHED;
136                        onAttach();
137                }
138                return transientModelObject;
139        }
140
141        /**
142         * Gets the attached status of this model instance
143         * 
144         * @return true if the model is attached, false otherwise
145         */
146        public final boolean isAttached()
147        {
148                return state == InternalState.ATTACHED;
149        }
150
151        @Override
152        public String toString()
153        {
154                StringBuilder sb = new StringBuilder(super.toString());
155                sb.append(":attached=")
156                        .append(isAttached())
157                        .append(":transientModelObject=[")
158                        .append(this.transientModelObject)
159                        .append(']');
160                return sb.toString();
161        }
162
163        /**
164         * Loads and returns the (temporary) model object.
165         * 
166         * @return the (temporary) model object
167         */
168        protected abstract T load();
169
170        /**
171         * Attaches to the current request. Implement this method with custom behavior, such as loading
172         * the model object.
173         */
174        protected void onAttach()
175        {
176        }
177
178        /**
179         * Detaches from the current request. Implement this method with custom behavior, such as
180         * setting the model object to null.
181         */
182        protected void onDetach()
183        {
184        }
185
186        /**
187         * Manually loads the model with the specified object. Subsequent calls to {@link #getObject()}
188         * will return {@code object} until {@link #detach()} is called.
189         * 
190         * @param object
191         *            The object to set into the model
192         */
193        @Override
194        public void setObject(final T object)
195        {
196                state = InternalState.ATTACHED;
197                transientModelObject = object;
198        }
199        
200        /**
201         * Create a {@link LoadableDetachableModel} for the given supplier.
202         *
203         * @param <T>
204         * @param getter Used for the getObject() method.
205         * @return the model
206         */
207        public static <T> LoadableDetachableModel<T> of(SerializableSupplier<T> getter)
208        {
209                return new LoadableDetachableModel<T>()
210                {
211                        private static final long serialVersionUID = 1L;
212
213                        @Override
214                        protected T load()
215                        {
216                                return getter.get();
217                        }
218                };
219        }
220}