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}