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 }