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.io.Serializable;
020
021import org.apache.wicket.Session;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025
026/**
027 * This model and its subclasses support chaining of IModels. {@code getObject()} of 
028 * {@code ChainingModel} returns its object like this:
029 * 
030 * <pre>
031 * if ( object instanceof IModel) { return ((IModel)object).getObject()}
032 * else return object;
033 * </pre>
034 * 
035 * ChainingModel also detaches the inner model on detach.
036 * 
037 * @param <T>
038 *            The Model object type
039 * 
040 * @see CompoundPropertyModel
041 * @see AbstractPropertyModel
042 * 
043 * @since 6.0.0
044 */
045public class ChainingModel<T> implements IModel<T>
046{
047        private static final Logger LOG = LoggerFactory.getLogger(ChainingModel.class);
048
049        /** Any model object (which may or may not implement IModel) */
050        private Object target;
051
052        public ChainingModel(final Object modelObject)
053        {
054                if (modelObject instanceof Session)
055                {
056                        LOG.warn("It is not a good idea to reference the Session instance "
057                                        + "in models directly as it may lead to serialization problems. "
058                                        + "If you need to access a property of the session via the model use the "
059                                        + "page instance as the model object and 'session.attribute' as the path.");
060                } else if (modelObject != null && (modelObject instanceof Serializable == false))
061                {
062                        LOG.warn("It is not a good idea to reference non-serializable {} "
063                                        + "in a model directly as it may lead to serialization problems.", modelObject.getClass());
064                }
065
066                target = modelObject;
067        }
068
069        /**
070         * Unsets this property model's instance variables and detaches the model.
071         *
072         * @see org.apache.wicket.model.IDetachable#detach()
073         */
074        @Override
075        public void detach()
076        {
077                // Detach nested object if it's a detachable
078                if (target instanceof IDetachable)
079                {
080                        ((IDetachable)target).detach();
081                }
082        }
083
084        @Override
085        @SuppressWarnings("unchecked")
086        public void setObject(T object)
087        {
088                if (target instanceof IModel)
089                {
090                        ((IModel<T>)target).setObject(object);
091                }
092                else
093                {
094                        target = object;
095                }
096        }
097
098        @Override
099        @SuppressWarnings("unchecked")
100        public T getObject()
101        {
102                if (target instanceof IModel)
103                {
104                        return ((IModel<T>)target).getObject();
105                }
106                return (T)target;
107        }
108
109        /**
110         * @return The target - object or model
111         */
112        public final Object getTarget()
113        {
114                return target;
115        }
116
117        /**
118         * Sets a new target - object or model
119         * @return this object
120         */
121        protected final ChainingModel<T> setTarget(final Object modelObject)
122        {
123                this.target = modelObject;
124                return this;
125        }
126        
127        /**
128         * @return The target - if it is a model, null otherwise
129         */
130        public IModel<?> getChainedModel()
131        {
132                if (target instanceof IModel)
133                {
134                        return (IModel<?>)target;
135                }
136                return null;
137        }
138
139        @Override
140        public String toString()
141        {
142                StringBuilder sb = new StringBuilder("Model:classname=[");
143                sb.append(getClass().getName()).append(']');
144                sb.append(":nestedModel=[").append(target).append(']');
145                return sb.toString();
146        }
147
148        /**
149         * @return The innermost model or the object if the target is not a model
150         */
151        public final Object getInnermostModelOrObject()
152        {
153                Object object = getTarget();
154                while (object instanceof IModel)
155                {
156                        Object tmp = ((IModel<?>)object).getObject();
157                        if (tmp == object)
158                        {
159                                break;
160                        }
161                        object = tmp;
162                }
163                return object;
164        }
165}