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}