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