001 package org.apache.fulcrum.intake.model; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import java.util.ArrayList; 023 import java.util.HashMap; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Map; 027 028 import org.apache.commons.logging.Log; 029 import org.apache.commons.logging.LogFactory; 030 import org.apache.commons.pool.BaseKeyedPoolableObjectFactory; 031 import org.apache.fulcrum.intake.IntakeException; 032 import org.apache.fulcrum.intake.IntakeServiceFacade; 033 import org.apache.fulcrum.intake.Retrievable; 034 import org.apache.fulcrum.intake.xmlmodel.AppData; 035 import org.apache.fulcrum.intake.xmlmodel.XmlField; 036 import org.apache.fulcrum.intake.xmlmodel.XmlGroup; 037 import org.apache.fulcrum.parser.ValueParser; 038 039 /** 040 * Holds a group of Fields 041 * 042 * @author <a href="mailto:jmcnally@collab.net">John McNally</a> 043 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 044 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a> 045 * @version $Id: Group.java 832048 2009-11-02 18:55:08Z tv $ 046 */ 047 public class Group 048 { 049 public static final String EMPTY = ""; 050 051 /* 052 * An id representing a new object. 053 */ 054 public static final String NEW = "_0"; 055 056 private static final Log log; 057 private static final boolean isDebugEnabled; 058 059 static 060 { 061 log = LogFactory.getLog(Group.class); 062 isDebugEnabled = log.isDebugEnabled(); 063 } 064 065 /** 066 * The key used to represent this group in a parameter. 067 * This key is usually a prefix as part of a field key. 068 */ 069 protected final String gid; 070 071 /** 072 * The name used in templates and java code to refer to this group. 073 */ 074 protected final String name; 075 076 /** 077 * The number of Groups with the same name that will be pooled. 078 */ 079 private final int poolCapacity; 080 081 /** 082 * A map of the fields in this group mapped by field name. 083 */ 084 protected Map fields; 085 086 /** 087 * Map of the fields by mapToObject 088 */ 089 protected Map mapToObjectFields; 090 091 /** 092 * An array of fields in this group. 093 */ 094 protected Field[] fieldsArray; 095 096 /** 097 * The object id used to associate this group to a bean 098 * for one request cycle 099 */ 100 protected String oid; 101 102 /** 103 * The object containing the request data 104 */ 105 protected ValueParser pp; 106 107 /** 108 * A flag to help prevent duplicate hidden fields declaring this group. 109 */ 110 protected boolean isDeclared; 111 112 /** 113 * Constructs a new Group based on the xml specification. Groups are 114 * instantiated and pooled by the IntakeService and should not 115 * be instantiated otherwise. 116 * 117 * @param group a <code>XmlGroup</code> value 118 * @exception IntakeException if an error occurs in other classes 119 */ 120 public Group(XmlGroup group) throws IntakeException 121 { 122 gid = group.getKey(); 123 name = group.getName(); 124 poolCapacity = Integer.parseInt(group.getPoolCapacity()); 125 126 List inputFields = group.getFields(); 127 int size = inputFields.size(); 128 fields = new HashMap((int) (1.25 * size + 1)); 129 mapToObjectFields = new HashMap((int) (1.25 * size + 1)); 130 fieldsArray = new Field[size]; 131 for (int i = size - 1; i >= 0; i--) 132 { 133 XmlField f = (XmlField) inputFields.get(i); 134 Field field = FieldFactory.getInstance(f, this); 135 fieldsArray[i] = field; 136 fields.put(f.getName(), field); 137 138 // map fields by their mapToObject 139 List tmpFields = (List) mapToObjectFields.get(f.getMapToObject()); 140 if (tmpFields == null) 141 { 142 tmpFields = new ArrayList(size); 143 mapToObjectFields.put(f.getMapToObject(), tmpFields); 144 } 145 tmpFields.add(field); 146 } 147 148 // Change the mapToObjectFields values to Field[] 149 for (Iterator keys = mapToObjectFields.keySet().iterator(); keys.hasNext();) 150 { 151 Object key = keys.next(); 152 List tmpFields = (List) mapToObjectFields.get(key); 153 mapToObjectFields.put(key, 154 tmpFields.toArray(new Field[tmpFields.size()])); 155 } 156 } 157 158 /** 159 * Initializes the default Group using parameters. 160 * 161 * @param pp a <code>ValueParser</code> value 162 * @return this Group 163 */ 164 public Group init(ValueParser pp) throws IntakeException 165 { 166 return init(NEW, pp); 167 } 168 169 /** 170 * Initializes the Group with parameters from RunData 171 * corresponding to key. 172 * 173 * @param pp a <code>ValueParser</code> value 174 * @return this Group 175 */ 176 public Group init(String key, ValueParser pp) throws IntakeException 177 { 178 this.oid = key; 179 this.pp = pp; 180 for (int i = fieldsArray.length - 1; i >= 0; i--) 181 { 182 fieldsArray[i].init(pp); 183 } 184 return this; 185 } 186 187 /** 188 * Initializes the group with properties from an object. 189 * 190 * @param obj a <code>Persistent</code> value 191 * @return a <code>Group</code> value 192 */ 193 public Group init(Retrievable obj) 194 { 195 this.oid = obj.getQueryKey(); 196 197 Class cls = obj.getClass(); 198 while (cls != null) 199 { 200 Field[] flds = (Field[]) mapToObjectFields.get(cls.getName()); 201 if (flds != null) 202 { 203 for (int i = flds.length - 1; i >= 0; i--) 204 { 205 flds[i].init(obj); 206 } 207 } 208 209 // Also check any interfaces 210 Class[] interfaces = cls.getInterfaces(); 211 for (int idx = 0; idx < interfaces.length; idx++) 212 { 213 Field[] interfaceFields = 214 (Field[]) mapToObjectFields.get(interfaces[idx].getName()); 215 if (interfaceFields != null) 216 { 217 for (int i = 0; i < interfaceFields.length; i++) 218 { 219 interfaceFields[i].init(obj); 220 } 221 } 222 } 223 224 cls = cls.getSuperclass(); 225 } 226 227 return this; 228 } 229 230 /** 231 * Gets a list of the names of the fields stored in this object. 232 * 233 * @return A String array containing the list of names. 234 */ 235 public String[] getFieldNames() 236 { 237 String nameList[] = new String[fieldsArray.length]; 238 for (int i = 0; i < nameList.length; i++) 239 { 240 nameList[i] = fieldsArray[i].name; 241 } 242 return nameList; 243 } 244 245 /** 246 * Return the name given to this group. The long name is to 247 * avoid conflicts with the get(String key) method. 248 * 249 * @return a <code>String</code> value 250 */ 251 public String getIntakeGroupName() 252 { 253 return name; 254 } 255 256 /** 257 * Get the number of Group objects that will be pooled. 258 * 259 * @return an <code>int</code> value 260 */ 261 public int getPoolCapacity() 262 { 263 return poolCapacity; 264 } 265 266 /** 267 * Get the part of the key used to specify the group. 268 * This is specified in the key attribute in the xml file. 269 * 270 * @return a <code>String</code> value 271 */ 272 public String getGID() 273 { 274 return gid; 275 } 276 277 /** 278 * Get the part of the key that distinguishes a group 279 * from others of the same name. 280 * 281 * @return a <code>String</code> value 282 */ 283 public String getOID() 284 { 285 return oid; 286 } 287 288 /** 289 * Concatenation of gid and oid. 290 * 291 * @return a <code>String</code> value 292 */ 293 public String getObjectKey() 294 { 295 return gid + oid; 296 } 297 298 /** 299 * Describe <code>getObjects</code> method here. 300 * 301 * @param pp a <code>ValueParser</code> value 302 * @return an <code>ArrayList</code> value 303 * @exception IntakeException if an error occurs 304 */ 305 public ArrayList getObjects(ValueParser pp) throws IntakeException 306 { 307 ArrayList objs = null; 308 String[] oids = pp.getStrings(gid); 309 if (oids != null) 310 { 311 objs = new ArrayList(oids.length); 312 for (int i = oids.length - 1; i >= 0; i--) 313 { 314 objs.add(IntakeServiceFacade.getGroup(name).init(oids[i], pp)); 315 } 316 } 317 return objs; 318 } 319 320 /** 321 * Get the Field . 322 * @return Field. 323 * @throws IntakeException indicates the field could not be found. 324 */ 325 public Field get(String fieldName) 326 throws IntakeException 327 { 328 if (fields.containsKey(fieldName)) 329 { 330 return (Field) fields.get(fieldName); 331 } 332 else 333 { 334 throw new IntakeException("Intake Field name: " + fieldName + 335 " not found in Group " + name); 336 } 337 } 338 339 /** 340 * Performs an AND between all the fields in this group. 341 * 342 * @return a <code>boolean</code> value 343 */ 344 public boolean isAllValid() 345 { 346 boolean valid = true; 347 for (int i = fieldsArray.length - 1; i >= 0; i--) 348 { 349 valid &= fieldsArray[i].isValid(); 350 if (isDebugEnabled && !fieldsArray[i].isValid()) 351 { 352 log.debug("Group(" + oid + "): " + name + "; Field: " 353 + fieldsArray[i].name + "; value=" + 354 fieldsArray[i].getValue() + " is invalid!"); 355 } 356 } 357 return valid; 358 } 359 360 /** 361 * Calls a setter methods on obj, for fields which have been set. 362 * 363 * @param obj Object to be set with the values from the group. 364 * @throws IntakeException indicates that a failure occurred while 365 * executing the setter methods of the mapped object. 366 */ 367 public void setProperties(Object obj) throws IntakeException 368 { 369 Class cls = obj.getClass(); 370 371 while (cls != null) 372 { 373 if (isDebugEnabled) 374 { 375 log.debug("setProperties(" + cls.getName() + ")"); 376 } 377 378 Field[] flds = (Field[]) mapToObjectFields.get(cls.getName()); 379 if (flds != null) 380 { 381 for (int i = flds.length - 1; i >= 0; i--) 382 { 383 flds[i].setProperty(obj); 384 } 385 } 386 387 // Also check any interfaces 388 Class[] interfaces = cls.getInterfaces(); 389 for (int idx = 0; idx < interfaces.length; idx++) 390 { 391 Field[] interfaceFields = 392 (Field[]) mapToObjectFields.get(interfaces[idx].getName()); 393 if (interfaceFields != null) 394 { 395 for (int i = 0; i < interfaceFields.length; i++) 396 { 397 interfaceFields[i].setProperty(obj); 398 } 399 } 400 } 401 402 cls = cls.getSuperclass(); 403 } 404 405 log.debug("setProperties() finished"); 406 } 407 408 /** 409 * Calls a setter methods on obj, for fields which pass validity tests. 410 * In most cases one should call Intake.isAllValid() and then if that 411 * test passes call setProperties. Use this method when some data is 412 * known to be invalid, but you still want to set the object properties 413 * that are valid. 414 */ 415 public void setValidProperties(Object obj) 416 { 417 Class cls = obj.getClass(); 418 while (cls != null) 419 { 420 Field[] flds = (Field[]) mapToObjectFields.get(cls.getName()); 421 if (flds != null) 422 { 423 for (int i = flds.length - 1; i >= 0; i--) 424 { 425 try 426 { 427 flds[i].setProperty(obj); 428 } 429 catch (IntakeException e) 430 { 431 // just move on to next field 432 } 433 } 434 } 435 436 // Also check any interfaces 437 Class[] interfaces = cls.getInterfaces(); 438 for (int idx = 0; idx < interfaces.length; idx++) 439 { 440 Field[] interfaceFields = 441 (Field[]) mapToObjectFields.get(interfaces[idx].getName()); 442 if (interfaceFields != null) 443 { 444 for (int i = 0; i < interfaceFields.length; i++) 445 { 446 try 447 { 448 interfaceFields[i].setProperty(obj); 449 } 450 catch(IntakeException e) 451 { 452 // just move on to next field 453 } 454 } 455 } 456 } 457 458 cls = cls.getSuperclass(); 459 } 460 } 461 462 /** 463 * Calls getter methods on objects that are known to Intake 464 * so that field values in forms can be initialized from 465 * the values contained in the intake tool. 466 * 467 * @param obj Object that will be used to as a source of data for 468 * setting the values of the fields within the group. 469 * @throws IntakeException indicates that a failure occurred while 470 * executing the setter methods of the mapped object. 471 */ 472 public void getProperties(Object obj) throws IntakeException 473 { 474 Class cls = obj.getClass(); 475 while (cls != null) 476 { 477 Field[] flds = (Field[]) mapToObjectFields.get(cls.getName()); 478 if (flds != null) 479 { 480 for (int i = flds.length - 1; i >= 0; i--) 481 { 482 flds[i].getProperty(obj); 483 } 484 } 485 486 // Also check any interfaces 487 Class[] interfaces = cls.getInterfaces(); 488 for (int idx = 0; idx < interfaces.length; idx++) 489 { 490 Field[] interfaceFields = 491 (Field[]) mapToObjectFields.get(interfaces[idx].getName()); 492 if (interfaceFields != null) 493 { 494 for (int i = 0; i < interfaceFields.length; i++) 495 { 496 interfaceFields[i].getProperty(obj); 497 } 498 } 499 } 500 501 cls = cls.getSuperclass(); 502 } 503 } 504 505 /** 506 * Removes references to this group and its fields from the 507 * query parameters 508 */ 509 public void removeFromRequest() 510 { 511 if (pp != null) 512 { 513 String[] groups = pp.getStrings(gid); 514 if (groups != null) 515 { 516 pp.remove(gid); 517 for (int i = 0; i < groups.length; i++) 518 { 519 if (groups[i] != null && !groups[i].equals(oid)) 520 { 521 pp.add(gid, groups[i]); 522 } 523 } 524 for (int i = fieldsArray.length - 1; i >= 0; i--) 525 { 526 fieldsArray[i].removeFromRequest(); 527 } 528 } 529 } 530 } 531 532 /** 533 * To be used in the event this group is used within multiple 534 * forms within the same template. 535 */ 536 public void resetDeclared() 537 { 538 isDeclared = false; 539 } 540 541 /** 542 * A xhtml valid hidden input field that notifies intake of the 543 * group's presence. 544 * 545 * @return a <code>String</code> value 546 */ 547 public String getHtmlFormInput() 548 { 549 StringBuffer sb = new StringBuffer(64); 550 appendHtmlFormInput(sb); 551 return sb.toString(); 552 } 553 554 /** 555 * A xhtml valid hidden input field that notifies intake of the 556 * group's presence. 557 */ 558 public void appendHtmlFormInput(StringBuffer sb) 559 { 560 if (!isDeclared) 561 { 562 isDeclared = true; 563 sb.append("<input type=\"hidden\" name=\"") 564 .append(gid) 565 .append("\" value=\"") 566 .append(oid) 567 .append("\"/>\n"); 568 } 569 } 570 571 // ********** PoolableObjectFactory implementation ****************** 572 573 public static class GroupFactory 574 extends BaseKeyedPoolableObjectFactory 575 { 576 private AppData appData; 577 578 public GroupFactory(AppData appData) 579 { 580 this.appData = appData; 581 } 582 583 /** 584 * Creates an instance that can be returned by the pool. 585 * @return an instance that can be returned by the pool. 586 * @throws IntakeException indicates that the group could not be retreived 587 */ 588 public Object makeObject(Object key) throws IntakeException 589 { 590 return new Group(appData.getGroup((String) key)); 591 } 592 593 /** 594 * Uninitialize an instance to be returned to the pool. 595 * @param obj the instance to be passivated 596 */ 597 public void passivateObject(Object key, Object obj) 598 { 599 Group group = (Group) obj; 600 group.oid = null; 601 group.pp = null; 602 for (int i = group.fieldsArray.length - 1; i >= 0; i--) 603 { 604 group.fieldsArray[i].dispose(); 605 } 606 group.isDeclared = false; 607 } 608 } 609 } 610 611