001    // Copyright 2007, 2008, 2009 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.corelib.base;
016    
017    import org.apache.tapestry5.Block;
018    import org.apache.tapestry5.MarkupWriter;
019    import org.apache.tapestry5.PropertyConduit;
020    import org.apache.tapestry5.PropertyOverrides;
021    import org.apache.tapestry5.annotations.Parameter;
022    import org.apache.tapestry5.beaneditor.PropertyModel;
023    import org.apache.tapestry5.ioc.Messages;
024    import org.apache.tapestry5.ioc.annotations.Inject;
025    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
026    import org.apache.tapestry5.services.BeanBlockSource;
027    import org.apache.tapestry5.services.Core;
028    import org.apache.tapestry5.services.Environment;
029    import org.apache.tapestry5.services.PropertyOutputContext;
030    
031    /**
032     * Base class for components that output a property value using a {@link PropertyModel}. There's a relationship between
033     * such a component and its container, as the container may provide messages in its message catalog needed by the {@link
034     * Block}s that render the values. In addition, the component may be passed Block parameters that are output overrides
035     * for specified properties.
036     * <p/>
037     * Subclasses will implement a <code>beginRender()</code> method that invokes {@link #renderPropertyValue(MarkupWriter,
038     * String)}.
039     *
040     * @see BeanBlockSource
041     */
042    public abstract class AbstractPropertyOutput
043    {
044        /**
045         * Model for property displayed by the cell.
046         */
047        @Parameter(required = true, allowNull = false)
048        private PropertyModel model;
049    
050        /**
051         * Used to search for block parameter overrides (this is normally the enclosing Grid component's resources).
052         */
053        @Parameter(required = true, allowNull = false)
054        private PropertyOverrides overrides;
055    
056        /**
057         * Identifies the object being rendered. The component will extract a property from the object and render its value
058         * (or delegate to a {@link org.apache.tapestry5.Block} that will do so).
059         */
060        @Parameter(required = true)
061        private Object object;
062    
063        /**
064         * Source for property display blocks. This defaults to the default implementation of {@link
065         * org.apache.tapestry5.services.BeanBlockSource}.
066         */
067        @Parameter(required = true, allowNull = false)
068        private BeanBlockSource beanBlockSource;
069    
070        @Inject
071        @Core
072        private BeanBlockSource defaultBeanBlockSource;
073    
074        @Inject
075        private Environment environment;
076    
077        private boolean mustPopEnvironment;
078    
079        BeanBlockSource defaultBeanBlockSource()
080        {
081            return defaultBeanBlockSource;
082        }
083    
084        protected PropertyModel getPropertyModel()
085        {
086            return model;
087        }
088    
089        /**
090         * Invoked from subclasses to do the rendering. The subclass controls the naming convention for locating an
091         * overriding Block parameter (it is the name of the property possibly suffixed with a value).
092         */
093        protected Object renderPropertyValue(MarkupWriter writer, String overrideBlockId)
094        {
095            Block override = overrides.getOverrideBlock(overrideBlockId);
096    
097            if (override != null) return override;
098    
099            String datatype = model.getDataType();
100    
101            if (beanBlockSource.hasDisplayBlock(datatype))
102            {
103                PropertyOutputContext context = new PropertyOutputContext()
104                {
105                    public Messages getMessages()
106                    {
107                        return overrides.getOverrideMessages();
108                    }
109    
110                    public Object getPropertyValue()
111                    {
112                        return readPropertyForObject();
113                    }
114    
115                    public String getPropertyId()
116                    {
117                        return model.getId();
118                    }
119    
120                    public String getPropertyName()
121                    {
122                        return model.getPropertyName();
123                    }
124                };
125    
126                environment.push(PropertyOutputContext.class, context);
127                mustPopEnvironment = true;
128    
129                return beanBlockSource.getDisplayBlock(datatype);
130            }
131    
132            Object value = readPropertyForObject();
133    
134            String text = value == null ? "" : value.toString();
135    
136            if (InternalUtils.isNonBlank(text))
137            {
138                writer.write(text);
139            }
140    
141            // Don't render anything else
142    
143            return false;
144        }
145    
146        Object readPropertyForObject()
147        {
148            PropertyConduit conduit = model.getConduit();
149    
150            try
151            {
152                return conduit == null ? null : conduit.get(object);
153            }
154            catch (NullPointerException ex)
155            {
156                throw new NullPointerException(BaseMessages.nullValueInPath(model.getPropertyName()));
157            }
158        }
159    
160        /**
161         * Returns false; there's no template and this prevents the body from rendering.
162         */
163        boolean beforeRenderTemplate()
164        {
165            return false;
166        }
167    
168        void afterRender()
169        {
170            if (mustPopEnvironment)
171            {
172                environment.pop(PropertyOutputContext.class);
173                mustPopEnvironment = false;
174            }
175        }
176    
177        // Used for testing.
178        void inject(final PropertyModel model, final Object object)
179        {
180            this.model = model;
181            this.object = object;
182        }
183    }