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
015package org.apache.tapestry5.corelib.base;
016
017import org.apache.tapestry5.Block;
018import org.apache.tapestry5.MarkupWriter;
019import org.apache.tapestry5.PropertyConduit;
020import org.apache.tapestry5.PropertyOverrides;
021import org.apache.tapestry5.annotations.Parameter;
022import org.apache.tapestry5.beaneditor.PropertyModel;
023import org.apache.tapestry5.ioc.Messages;
024import org.apache.tapestry5.ioc.annotations.Inject;
025import org.apache.tapestry5.ioc.internal.util.InternalUtils;
026import org.apache.tapestry5.services.BeanBlockSource;
027import org.apache.tapestry5.services.Core;
028import org.apache.tapestry5.services.Environment;
029import 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 */
042public 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}