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 }