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