1 /* 2 * Copyright 2012 Marek Romanowski 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package pl.matsuo.maven.skins.msb3; 17 18 import org.apache.maven.doxia.site.decoration.DecorationModel; 19 import org.apache.maven.project.MavenProject; 20 import org.apache.velocity.tools.ToolContext; 21 import org.apache.velocity.tools.config.DefaultKey; 22 import org.apache.velocity.tools.generic.SafeConfig; 23 import org.apache.velocity.tools.generic.ValueParser; 24 import org.codehaus.plexus.util.xml.Xpp3Dom; 25 26 /** 27 * An Apache Velocity tool that simplifies retrieval of custom configuration values for a 28 * Maven Site. 29 * <p> 30 * The tool is configured to access Maven site configuration of a skin inside {@code <custom>} 31 * element of site descriptor. It supports global properties (defined at skin level) and per-page 32 * properties (defined in {@code <page><mypage>} element). The per-page properties override the 33 * global ones. 34 * </p> 35 * <p> 36 * A sample configuration would be like that: 37 * </p> 38 * 39 * <pre> 40 * {@code 41 * <custom> 42 * <msb3Skin> 43 * <prop1>value1</prop1> 44 * <prop2> 45 * <prop21>value2</prop21> 46 * </prop2> 47 * <pages> 48 * <mypage project="myproject"> 49 * <prop1>override value1</prop1> 50 * </mypage> 51 * </pages> 52 * </msb3Skin> 53 * </custom> 54 * } 55 * </pre> 56 * <p> 57 * To get the value of {@code prop1}, one would simply use {@code $config.prop1}. This would return 58 * "override value1". Then {@code $config.prop2} would return "value2" - the global value. 59 * </p> 60 * <p> 61 * The tool allows querying the value easily, falling back from page to global configuration to 62 * {@code null}, if none is available. It also provides convenience accessors for common values. 63 * </p> 64 * <p> 65 * Note 66 * </p> 67 * 68 * @author Andrius Velykis 69 * @since 1.0 70 */ 71 @DefaultKey("config") 72 public class SkinConfigTool extends SafeConfig { 73 74 public static final String DEFAULT_KEY = "config"; 75 76 /** By default use Msb3 skin configuration tag */ 77 public static final String SKIN_KEY = "msb3Skin"; 78 79 private String key = DEFAULT_KEY; 80 private String skinKey = SKIN_KEY; 81 82 /* Create dummy nodes to avoid null checks */ 83 private Xpp3Dom globalProperties = new Xpp3Dom(""); 84 private Xpp3Dom pageProperties = new Xpp3Dom(""); 85 private String namespace = ""; 86 87 private String projectId = null; 88 private String fileId = null; 89 // private String fileShortId = null; 90 91 /** 92 * {@inheritDoc} 93 * 94 * @see SafeConfig#configure(ValueParser) 95 */ 96 @Override 97 protected void configure(ValueParser values) { 98 String altkey = values.getString("key"); 99 if (altkey != null) { 100 setKey(altkey); 101 } 102 103 // allow changing skin key in the configuration 104 String altSkinKey = values.getString("skinKey"); 105 if (altSkinKey != null) { 106 this.skinKey = altSkinKey; 107 } 108 109 // retrieve the decoration model from Velocity context 110 Object velocityContext = values.get("velocityContext"); 111 112 if (!(velocityContext instanceof ToolContext)) { 113 return; 114 } 115 116 ToolContext ctxt = (ToolContext) velocityContext; 117 118 Object projectObj = ctxt.get("project"); 119 if (projectObj instanceof MavenProject) { 120 MavenProject project = (MavenProject) projectObj; 121 String artifactId = project.getArtifactId(); 122 // use artifactId "sluggified" as the projectId 123 projectId = HtmlTool.slug(artifactId); 124 } 125 126 // calculate the page ID from the current file name 127 Object currentFileObj = ctxt.get("currentFileName"); 128 if (currentFileObj instanceof String) { 129 130 String currentFile = (String) currentFileObj; 131 132 // drop the extension 133 int lastDot = currentFile.lastIndexOf("."); 134 if (lastDot >= 0) { 135 currentFile = currentFile.substring(0, lastDot); 136 } 137 138 // get the short ID (in case of nested files) 139 // String fileName = new File(currentFile).getName(); 140 // fileShortId = HtmlTool.slug(fileName); 141 142 // full file ID includes the nested dirs 143 // replace nesting "/" with "-" 144 fileId = HtmlTool.slug(currentFile.replace("/", "-").replace("\\", "-")); 145 } 146 147 Object decorationObj = ctxt.get("decoration"); 148 149 if (!(decorationObj instanceof DecorationModel)) { 150 return; 151 } 152 153 DecorationModel decoration = (DecorationModel) decorationObj; 154 Object customObj = decoration.getCustom(); 155 156 if (!(customObj instanceof Xpp3Dom)) { 157 return; 158 } 159 160 // Now that we have the custom node, get the global properties 161 // under the skin tag 162 Xpp3Dom customNode = (Xpp3Dom) customObj; 163 Xpp3Dom skinNode = customNode.getChild(skinKey); 164 String namespaceKey = ":" + skinKey; 165 166 if (skinNode == null) { 167 // try searching with any namespace 168 for (Xpp3Dom child : customNode.getChildren()) { 169 if (child.getName().endsWith(namespaceKey)) { 170 skinNode = child; 171 break; 172 } 173 } 174 } 175 176 if (skinNode != null) { 177 globalProperties = skinNode; 178 179 if (skinNode.getName().endsWith(namespaceKey)) { 180 // extract the namespace (including the colon) 181 namespace = skinNode.getName().substring(0, skinNode.getName().length() - namespaceKey.length() + 1); 182 } 183 184 // for page properties, retrieve the file name and drop the `.html` 185 // extension - this will be used, i.e. `index` instead of `index.html` 186 Xpp3Dom pagesNode = getChild(skinNode, "pages"); 187 if (pagesNode != null) { 188 189 // Get the page for the file 190 // TODO try fileShortId as well? 191 Xpp3Dom page = getChild(pagesNode, fileId); 192 193 // Now check if the project artifact ID is set, and if so, if it matches the 194 // current project. This allows preventing accidental reuse of parent page 195 // configs in children modules 196 if (page != null && projectId != null) { 197 String pageProject = page.getAttribute("project"); 198 if (pageProject != null && !projectId.equals(pageProject)) { 199 // project ID indicated, and is different - do not use the config 200 page = null; 201 } 202 } 203 204 if (page != null) { 205 pageProperties = page; 206 } 207 } 208 } 209 } 210 211 /** 212 * Retrieves the child node. Tests both default name and with namespace. 213 * 214 * @param parentNode 215 * @param name 216 * @return 217 */ 218 private Xpp3Dom getChild(Xpp3Dom parentNode, String name) { 219 Xpp3Dom child = parentNode.getChild(name); 220 if (child != null) { 221 return child; 222 } 223 224 return parentNode.getChild(namespace + name); 225 } 226 227 /** 228 * Sets the key under which this tool has been configured. 229 * 230 * @since 1.0 231 */ 232 protected void setKey(String key) { 233 if (key == null) { 234 throw new NullPointerException("SkinConfigTool key cannot be null"); 235 } 236 this.key = key; 237 } 238 239 /** 240 * Should return the key under which this tool has been configured. The default is `config`. 241 * 242 * @since 1.0 243 */ 244 public String getKey() { 245 return this.key; 246 } 247 248 /** 249 * Default accessor for config properties. Instead of using {@code $config.get("myproperty")}, 250 * one can utilise Velocity fallback onto the default getter and use {@code $config.myproperty}. 251 * 252 * @param property 253 * the property of interest 254 * @return configuration node if found in the following sequence: 255 * <ol> 256 * <li>In page configuration</li> 257 * <li>In global configuration</li> 258 * <li>{@code null} otherwise</li> 259 * </ol> 260 * @since 1.0 261 */ 262 public Xpp3Dom get(String property) { 263 264 // first try page properties 265 Xpp3Dom propNode = getChild(pageProperties, property); 266 if (propNode == null) { 267 // try global 268 propNode = getChild(globalProperties, property); 269 } 270 271 return propNode; 272 } 273 274 /** 275 * Retrieves the text value of the given {@code property}, e.g. as in 276 * {@code <myprop>value</myprop>}. 277 * 278 * @param property 279 * the property of interest 280 * @return the configuration value if found in page or globally, {@code null} otherwise. 281 * @see #get(String) 282 * @since 1.0 283 */ 284 public String value(String property) { 285 286 Xpp3Dom propNode = get(property); 287 288 if (propNode == null) { 289 // not found 290 return null; 291 } 292 293 return propNode.getValue(); 294 } 295 296 /** 297 * A convenience method to check if the value of the {@code property} is {@code "true"}. 298 * 299 * @param property 300 * the property of interest 301 * @return {@code true} if the configuration value is set either in page or globally, and is 302 * equal to {@code "true"}. 303 * @see #get(String) 304 * @since 1.0 305 */ 306 public boolean is(String property) { 307 return "true".equals(value(property)); 308 } 309 310 /** 311 * A convenience method to check if the value of the {@code property} is {@code "false"}. Useful 312 * for properties that are enabled by default - checks if the property is set to {@code "false"} 313 * explicitly. 314 * 315 * @param property 316 * the property of interest 317 * @return {@code true} if the configuration value is set either in page or globally, and is 318 * equal to {@code "false"}. Note that this will return {@code false} if property is not 319 * set at all. 320 * @see #get(String) 321 * @since 1.0 322 */ 323 public boolean not(String property) { 324 return "false".equals(value(property)); 325 } 326 327 /** 328 * A convenience method to check if the {@code property} is set to a specific value. 329 * 330 * @param property 331 * the property of interest 332 * @param value 333 * the property value to check 334 * @return {@code true} if the configuration value is set either in page or globally, and is 335 * equal to {@code value}. 336 * @see #get(String) 337 * @since 1.0 338 */ 339 public boolean isValue(String property, String value) { 340 return value != null && value.equals(value(property)); 341 } 342 343 public String getProjectId() { 344 return projectId; 345 } 346 347 public String getFileId() { 348 return fileId; 349 } 350 351 }