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