    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    }