001package org.apache.turbine.services.intake; 002 003 004/* 005 * Licensed to the Apache Software Foundation (ASF) under one 006 * or more contributor license agreements. See the NOTICE file 007 * distributed with this work for additional information 008 * regarding copyright ownership. The ASF licenses this file 009 * to you under the Apache License, Version 2.0 (the 010 * "License"); you may not use this file except in compliance 011 * with the License. You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, 016 * software distributed under the License is distributed on an 017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 018 * KIND, either express or implied. See the License for the 019 * specific language governing permissions and limitations 020 * under the License. 021 */ 022 023 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.commons.lang.ArrayUtils; 029import org.apache.fulcrum.intake.IntakeException; 030import org.apache.fulcrum.intake.IntakeService; 031import org.apache.fulcrum.intake.Retrievable; 032import org.apache.fulcrum.intake.model.Group; 033import org.apache.fulcrum.parser.ValueParser; 034import org.apache.fulcrum.pool.Recyclable; 035import org.apache.logging.log4j.LogManager; 036import org.apache.logging.log4j.Logger; 037import org.apache.turbine.annotation.TurbineService; 038import org.apache.turbine.services.pull.ApplicationTool; 039import org.apache.turbine.util.RunData; 040 041 042/** 043 * The main class through which Intake is accessed. Provides easy access 044 * to the Fulcrum Intake component. 045 * 046 * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a> 047 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 048 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a> 049 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 050 * @version $Id$ 051 */ 052public class IntakeTool 053 implements ApplicationTool, Recyclable 054{ 055 /** Used for logging */ 056 protected static final Logger log = LogManager.getLogger(IntakeTool.class); 057 058 /** Constant for default key */ 059 public static final String DEFAULT_KEY = "_0"; 060 061 /** Constant for the hidden fieldname */ 062 public static final String INTAKE_GRP = "intake-grp"; 063 064 /** Groups from intake.xml */ 065 protected HashMap<String, Group> groups = null; 066 067 /** ValueParser instance */ 068 protected ValueParser pp; 069 070 private final HashMap<String, Group> declaredGroups = new HashMap<>(); 071 private final StringBuilder allGroupsSB = new StringBuilder(256); 072 private final StringBuilder groupSB = new StringBuilder(128); 073 074 /** The cache of PullHelpers. **/ 075 private Map<String, IntakeTool.PullHelper> pullMap = null; 076 077 /** 078 * The Intake service. 079 */ 080 @TurbineService 081 protected IntakeService intakeService; 082 083 /** 084 * Constructor 085 */ 086 public IntakeTool() 087 { 088 } 089 090 /** 091 * Prepares intake for a single request 092 */ 093 @Override 094 public void init(Object runData) 095 { 096 if (groups == null) // Initialize only once 097 { 098 String[] groupNames = intakeService.getGroupNames(); 099 int groupCount = 0; 100 if (groupNames != null) 101 { 102 groupCount = groupNames.length; 103 } 104 groups = new HashMap<>((int) (1.25 * groupCount + 1)); 105 pullMap = new HashMap<>((int) (1.25 * groupCount + 1)); 106 107 for (int i = groupCount - 1; i >= 0; i--) 108 { 109 pullMap.put(groupNames[i], new PullHelper(groupNames[i])); 110 } 111 } 112 113 this.pp = ((RunData) runData).getParameters(); 114 115 String[] groupKeys = pp.getStrings(INTAKE_GRP); 116 String[] groupNames = null; 117 if (ArrayUtils.isEmpty(groupKeys)) 118 { 119 groupNames = intakeService.getGroupNames(); 120 } 121 else 122 { 123 groupNames = new String[groupKeys.length]; 124 for (int i = groupKeys.length - 1; i >= 0; i--) 125 { 126 groupNames[i] = intakeService.getGroupName(groupKeys[i]); 127 } 128 } 129 130 for (int i = groupNames.length - 1; i >= 0; i--) 131 { 132 try 133 { 134 List<Group> foundGroups = intakeService.getGroup(groupNames[i]) 135 .getObjects(pp); 136 137 if (foundGroups != null) 138 { 139 foundGroups.forEach( 140 group -> groups.put(group.getObjectKey(), group)); 141 } 142 } 143 catch (IntakeException e) 144 { 145 log.error(e); 146 } 147 } 148 } 149 150 /** 151 * Add all registered group ids to the value parser 152 * 153 * @param vp the value parser 154 */ 155 public void addGroupsToParameters(ValueParser vp) 156 { 157 for (Group group : groups.values()) 158 { 159 if (!declaredGroups.containsKey(group.getIntakeGroupName())) 160 { 161 declaredGroups.put(group.getIntakeGroupName(), null); 162 vp.add("intake-grp", group.getGID()); 163 } 164 vp.add(group.getGID(), group.getOID()); 165 } 166 declaredGroups.clear(); 167 } 168 169 /** 170 * A convenience method to write out the hidden form fields 171 * that notify intake of the relevant groups. It should be used 172 * only in templates with 1 form. In multiform templates, the groups 173 * that are relevant for each form need to be declared using 174 * $intake.newForm() and $intake.declareGroup($group) for the relevant 175 * groups in the form. 176 * 177 * @return the HTML that declares all groups to Intake in hidden input fields 178 * 179 */ 180 public String declareGroups() 181 { 182 allGroupsSB.setLength(0); 183 for (Group group : groups.values()) 184 { 185 declareGroup(group, allGroupsSB); 186 } 187 return allGroupsSB.toString(); 188 } 189 190 /** 191 * A convenience method to write out the hidden form fields 192 * that notify intake of the group. 193 * 194 * @param group the group to declare 195 * @return the HTML that declares the group to Intake in a hidden input field 196 */ 197 public String declareGroup(Group group) 198 { 199 groupSB.setLength(0); 200 declareGroup(group, groupSB); 201 return groupSB.toString(); 202 } 203 204 /** 205 * xhtml valid hidden input field(s) that notifies intake of the 206 * group's presence. 207 * @param group the group to declare 208 * @param sb a String Builder where the hidden field HTML will be appended 209 */ 210 public void declareGroup(Group group, StringBuilder sb) 211 { 212 if (!declaredGroups.containsKey(group.getIntakeGroupName())) 213 { 214 declaredGroups.put(group.getIntakeGroupName(), null); 215 sb.append("<input type=\"hidden\" name=\"") 216 .append(INTAKE_GRP) 217 .append("\" value=\"") 218 .append(group.getGID()) 219 .append("\"/>\n"); 220 } 221 group.appendHtmlFormInput(sb); 222 } 223 224 /** 225 * Declare that a new form starts 226 */ 227 public void newForm() 228 { 229 declaredGroups.clear(); 230 for (Group group : groups.values()) 231 { 232 group.resetDeclared(); 233 } 234 } 235 236 /** 237 * Implementation of ApplicationTool interface is not needed for this 238 * tool as it is request scoped 239 */ 240 @Override 241 public void refresh() 242 { 243 // empty 244 } 245 246 /** 247 * Inner class to present a nice interface to the template designer 248 */ 249 public class PullHelper 250 { 251 /** Name of the group used by the pull helper */ 252 String groupName; 253 254 /** 255 * Protected constructor to force use of factory method. 256 * 257 * @param groupName the group name 258 */ 259 protected PullHelper(String groupName) 260 { 261 this.groupName = groupName; 262 } 263 264 /** 265 * Populates the object with the default values from the XML File 266 * 267 * @return a Group object with the default values 268 * @throws IntakeException if getting the group fails 269 */ 270 public Group getDefault() 271 throws IntakeException 272 { 273 return setKey(DEFAULT_KEY); 274 } 275 276 /** 277 * Calls setKey(key,true) 278 * 279 * @param key the group key 280 * @return an Intake Group 281 * @throws IntakeException if getting the group fails 282 */ 283 public Group setKey(String key) 284 throws IntakeException 285 { 286 return setKey(key, true); 287 } 288 289 /** 290 * Return the group identified by its key 291 * 292 * @param key the group key 293 * @param create true if a non-existing group should be created 294 * @return an Intake Group 295 * @throws IntakeException if getting the group fails 296 */ 297 public Group setKey(String key, boolean create) 298 throws IntakeException 299 { 300 Group g = null; 301 302 String inputKey = intakeService.getGroupKey(groupName) + key; 303 if (groups.containsKey(inputKey)) 304 { 305 g = groups.get(inputKey); 306 } 307 else if (create) 308 { 309 g = intakeService.getGroup(groupName); 310 groups.put(inputKey, g); 311 g.init(key, pp); 312 } 313 314 return g; 315 } 316 317 /** 318 * maps an Intake Group to the values from a Retrievable object. 319 * 320 * @param obj A retrievable object 321 * @return an Intake Group 322 */ 323 public Group mapTo(Retrievable obj) 324 { 325 Group g = null; 326 327 try 328 { 329 String inputKey = intakeService.getGroupKey(groupName) 330 + obj.getQueryKey(); 331 if (groups.containsKey(inputKey)) 332 { 333 g = groups.get(inputKey); 334 } 335 else 336 { 337 g = intakeService.getGroup(groupName); 338 groups.put(inputKey, g); 339 } 340 341 return g.init(obj); 342 } 343 catch (IntakeException e) 344 { 345 log.error(e); 346 } 347 348 return null; 349 } 350 } 351 352 /** 353 * get a specific group 354 * @param groupName the name of the group 355 * @return a {@link PullHelper} wrapper around the group 356 */ 357 public PullHelper get(String groupName) 358 { 359 return pullMap.get(groupName); 360 } 361 362 /** 363 * Get a specific group 364 * 365 * @param groupName the name of the group 366 * @param throwExceptions if false, exceptions will be suppressed. 367 * @return a {@link PullHelper} wrapper around the group 368 * @throws IntakeException could not retrieve group 369 */ 370 public PullHelper get(String groupName, boolean throwExceptions) 371 throws IntakeException 372 { 373 return pullMap.get(groupName); 374 } 375 376 /** 377 * Loops through all of the Groups and checks to see if 378 * the data within the Group is valid. 379 * @return true if all groups are valid 380 */ 381 public boolean isAllValid() 382 { 383 boolean allValid = true; 384 for (Group group : groups.values()) 385 { 386 allValid &= group.isAllValid(); 387 } 388 return allValid; 389 } 390 391 /** 392 * Get a specific group by name and key. 393 * @param groupName the name of the group 394 * @param key the key for the group 395 * @return the {@link Group} 396 * @throws IntakeException if the group could not be retrieved 397 */ 398 public Group get(String groupName, String key) 399 throws IntakeException 400 { 401 return get(groupName, key, true); 402 } 403 404 /** 405 * Get a specific group by name and key. Also specify 406 * whether or not you want to create a new group. 407 * @param groupName the name of the group 408 * @param key the key for the group 409 * @param create true if a new group should be created 410 * @return the {@link Group} 411 * @throws IntakeException if the group could not be retrieved 412 */ 413 public Group get(String groupName, String key, boolean create) 414 throws IntakeException 415 { 416 if (groupName == null) 417 { 418 throw new IntakeException("intakeService.get: groupName == null"); 419 } 420 if (key == null) 421 { 422 throw new IntakeException("intakeService.get: key == null"); 423 } 424 425 PullHelper ph = get(groupName); 426 return (ph == null) ? null : ph.setKey(key, create); 427 } 428 429 /** 430 * Removes group. Primary use is to remove a group that has 431 * been processed by an action and is no longer appropriate 432 * in the view (screen). 433 * @param group the group instance to remove 434 */ 435 public void remove(Group group) 436 { 437 if (group != null) 438 { 439 groups.remove(group.getObjectKey()); 440 group.removeFromRequest(); 441 442 String[] groupKeys = pp.getStrings(INTAKE_GRP); 443 444 pp.remove(INTAKE_GRP); 445 446 if (groupKeys != null) 447 { 448 for (String groupKey : groupKeys) 449 { 450 if (!groupKey.equals(group.getGID())) 451 { 452 pp.add(INTAKE_GRP, groupKey); 453 } 454 } 455 } 456 457 try 458 { 459 intakeService.releaseGroup(group); 460 } 461 catch (IntakeException ie) 462 { 463 log.error("Tried to release unknown group {}", group.getIntakeGroupName(), ie); 464 } 465 } 466 } 467 468 /** 469 * Removes all groups. Primary use is to remove groups that have 470 * been processed by an action and are no longer appropriate 471 * in the view (screen). 472 */ 473 public void removeAll() 474 { 475 Object[] allGroups = groups.values().toArray(); 476 for (int i = allGroups.length - 1; i >= 0; i--) 477 { 478 Group group = (Group) allGroups[i]; 479 remove(group); 480 } 481 } 482 483 /** 484 * Get a Map containing all the groups. 485 * 486 * @return the Group Map 487 */ 488 public Map<String, Group> getGroups() 489 { 490 return groups; 491 } 492 493 // ****************** Recyclable implementation ************************ 494 495 private boolean disposed; 496 497 /** 498 * Recycles the object for a new client. Recycle methods with 499 * parameters must be added to implementing object and they will be 500 * automatically called by pool implementations when the object is 501 * taken from the pool for a new client. The parameters must 502 * correspond to the parameters of the constructors of the object. 503 * For new objects, constructors can call their corresponding recycle 504 * methods whenever applicable. 505 * The recycle methods must call their super. 506 */ 507 @Override 508 public void recycle() 509 { 510 disposed = false; 511 } 512 513 /** 514 * Disposes the object after use. The method is called 515 * when the object is returned to its pool. 516 * The dispose method must call its super. 517 */ 518 @Override 519 public void dispose() 520 { 521 for (Group group : groups.values()) 522 { 523 try 524 { 525 intakeService.releaseGroup(group); 526 } 527 catch (IntakeException ie) 528 { 529 log.error("Tried to release unknown group {}", 530 group.getIntakeGroupName(), ie); 531 } 532 } 533 534 groups.clear(); 535 declaredGroups.clear(); 536 pp = null; 537 538 disposed = true; 539 } 540 541 /** 542 * Checks whether the recyclable has been disposed. 543 * 544 * @return true, if the recyclable is disposed. 545 */ 546 @Override 547 public boolean isDisposed() 548 { 549 return disposed; 550 } 551}