View Javadoc

1   /*
2    * Copyright (c) 2005 Regents of the University of California (Regents). Created
3    * by TELS, Graduate School of Education, University of California at Berkeley.
4    *
5    * This software is distributed under the GNU Lesser General Public License, v2.
6    *
7    * Permission is hereby granted, without written agreement and without license
8    * or royalty fees, to use, copy, modify, and distribute this software and its
9    * documentation for any purpose, provided that the above copyright notice and
10   * the following two paragraphs appear in all copies of this software.
11   *
12   * REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
13   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
14   * PURPOSE. THE SOFTWAREAND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
15   * HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
16   * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17   *
18   * IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
19   * SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
20   * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
21   * REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22   */
23  package net.sf.sail.core.beans;
24  
25  import java.beans.beancontext.BeanContextServicesSupport;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.logging.Level;
36  import java.util.logging.Logger;
37  
38  import net.sf.sail.core.beans.assembly.Assignment;
39  import net.sf.sail.core.beans.assembly.PodRegistry;
40  import net.sf.sail.core.beans.assembly.PodVarDelegate;
41  import net.sf.sail.core.beans.assembly.PropertyRef;
42  import net.sf.sail.core.curnit.PodArchiveResolver;
43  import net.sf.sail.core.util.SailBeanUtils;
44  import net.sf.sail.core.uuid.PodUuid;
45  
46  /**
47   * Bean representing the reusable Pod container.
48   * 
49   * @author turadg
50   */
51  public class Pod extends BeanContextServicesSupport {
52  	/**
53  	 * 
54  	 */
55  	private static final long serialVersionUID = 7881255006647471835L;
56  
57  	/**
58  	 * Make this a constant so other classes can use this property name
59  	 */
60  	public static final String PROPERTY_POD_ID = "podId";
61  
62  	/**
63  	 * Logger for this class
64  	 */
65  	private static final Logger logger = Logger.getLogger(Pod.class.getName());
66  
67  	// property
68  	PodUuid podId;
69  
70  	public PodUuid getPodId() {
71  		return podId;
72  	}
73  
74  	public void setPodId(PodUuid newPodId) {
75  		if (newPodId == null)
76  			throw new NullPointerException("podId argument was null");
77  		PodUuid oldPodId = this.podId;
78  		this.podId = newPodId;
79  
80  		// firePropertyChange(PROPERTY_POD_ID, old, podId);
81  		// property change listener won't do because the pod may have never had
82  		// a podId (and thus not fit yet in the registry)
83  		PodRegistry.getDefaultRegistry().reregister(this);
84  
85  		if (oldPodId != null)
86  			// update in assembly assignment statements
87  			updateLeftAssignments(assemblyCalls, oldPodId, newPodId);
88  	}
89  
90  	/**
91  	 * This pod's id is often in the left of assignment statements in the list
92  	 * of assembly calls. Replace all occurences of the old pod id in the left
93  	 * assignments with the new pod id.
94  	 * 
95  	 * @param calls
96  	 * @param oldPodId
97  	 * @param newPodId
98  	 */
99  	private static void updateLeftAssignments(List<Assignment> calls,
100 			PodUuid oldPodId, PodUuid newPodId) {
101 		for (Assignment assignment : calls) {
102 			PodVarDelegate left = assignment.getLeft();
103 			if (left.getPodId().equals(oldPodId))
104 				left.setPodId(newPodId);
105 		}
106 	}
107 
108 	// property
109 	Map<String, PropertyRef> vars = new HashMap<String, PropertyRef>();
110 
111 	public Map<String, PropertyRef> getVars() {
112 		return vars;
113 	}
114 
115 	public void setVars(Map<String, PropertyRef> vars) {
116 		this.vars = vars;
117 	}
118 
119 	/**
120 	 * Export propertyRef as exportSocketName.
121 	 * 
122 	 * Export a property of a bean within this pod, given by propertyRef. This
123 	 * export will be referenced by exportSocketName so that other pods can ask
124 	 * for the value, using resolve().
125 	 * 
126 	 * When this pod is marshalled, the value of the property will be preserved.
127 	 * 
128 	 * @param propertyRef
129 	 * @param exportSocketName
130 	 */
131 	public void exportProperty(PropertyRef propertyRef, String exportSocketName) {
132 		bind(exportSocketName, propertyRef, false);
133 	}
134 
135 	/**
136 	 * Import propertyRef as importSocketName.
137 	 * 
138 	 * Import a property of a bean within this pod, given by propertyRef. This
139 	 * import will be referenced by importSocketName so that assembly calls can
140 	 * fill it in with other values.
141 	 * 
142 	 * When this pod is marshalled, the property value won't be preserved since
143 	 * it will be set by an assembly call at load time.
144 	 * 
145 	 * @param propertyRef
146 	 * @param importSocketName
147 	 */
148 	public void importProperty(PropertyRef propertyRef, String importSocketName) {
149 		bind(importSocketName, propertyRef, true);
150 	}
151 
152 	/**
153 	 * This should not be used outside this class. Instead use exportProperty()
154 	 * or importProperty() as applicable.
155 	 * 
156 	 * @param varName
157 	 * @param propertyRef
158 	 * @param isTransient
159 	 */
160 	@Deprecated
161 	public void bind(String varName, PropertyRef propertyRef,
162 			boolean isTransient) {
163 		Object target = propertyRef.getTarget();
164 		if (!this.contains(target))
165 			throw new RuntimeException("pod " + this
166 					+ " does not contain target " + target + " of binding");
167 		vars.put(varName, propertyRef);
168 		if (isTransient)
169 			transientRefs.add(propertyRef);
170 	}
171 
172 	/**
173 	 * 
174 	 * @param varName
175 	 * @return the current value of the property (import or export) specified by
176 	 *         varName
177 	 */
178 	public Object resolve(String varName) {
179 		Object value = null;
180 		try {
181 			value = (vars.get(varName)).getCurrentValue();
182 		} catch (Exception e) {
183 			logger.warning("exception: " + e);
184 		}
185 		return value;
186 	}
187 
188 	// property
189 	Set<PropertyRef> transientRefs = new HashSet<PropertyRef>();
190 
191 	public Set<PropertyRef> getTransientRefs() {
192 		return transientRefs;
193 	}
194 
195 	public void setTransientRefs(Set<PropertyRef> transientRefs) {
196 		this.transientRefs = transientRefs;
197 	}
198 
199 	// property
200 	List<Assignment> assemblyCalls = new LinkedList<Assignment>();
201 
202 	private boolean assemblyStarted;
203 
204 	private boolean assembled;
205 
206 	public List<Assignment> getAssemblyCalls() {
207 		return assemblyCalls;
208 	}
209 
210 	public void setAssemblyCalls(List<Assignment> assemblyCalls) {
211 		Object old = this.assemblyCalls;
212 		this.assemblyCalls = assemblyCalls;
213 		firePropertyChange("assemblyCalls", old, assemblyCalls);
214 	}
215 
216 	protected Pod childWithId(PodUuid podId) {
217 		Collection<Pod> podChildren = SailBeanUtils.childrenOfType(this,
218 				Pod.class);
219 		for (Pod pod : podChildren) {
220 			if (pod.getPodId().equals(podId))
221 				return pod;
222 		}
223 		return null;
224 	}
225 
226 	public URL getPodArchive() {
227 		return PodArchiveResolver.getPodArchive(podId);
228 	}
229 
230 	// TODO make this throw a checked exception for failed assignments
231 	public void assemble(PodRegistry registry) throws UnknownPodException {
232 		assembleChildren(registry);
233 		registry.register(this);
234 		assemblyStarted = true;
235 		for (Assignment assignment : assemblyCalls) {
236 			try {
237 				assignment.execute(registry);
238 			} catch (Exception e) {
239 				logger
240 						.severe("Pod assemble failing due failed execution of assignment " + assignment + " by " + e); //$NON-NLS-1$
241 				throw new RuntimeException("error assembling", e);
242 			}
243 		}
244 		assembled= true;
245 	}
246 
247 	// TODO write delegate that persists this without exposing it
248 	Set<PodUuid> childPodIds = new HashSet<PodUuid>();
249 
250 	@Override
251 	public boolean add(Object targetChild) {
252 		boolean success = super.add(targetChild);
253 		if (logger.isLoggable(Level.FINE)) {
254 			logger.fine(this + " added " + targetChild + " success:" + success);
255 		}
256 		if (success && targetChild instanceof Pod) {
257 			Pod pod = (Pod) targetChild;
258 			childPodIds.add(pod.getPodId());
259 		}
260 
261 		return success;
262 	}
263 
264 	/**
265 	 * Remove the member from this container.
266 	 * 
267 	 * If the member is a child pod, also remove all references from this pod to
268 	 * that pod.
269 	 */
270 	@Override
271 	public boolean remove(Object targetChild) {
272 		// remove as member of this pod container
273 		boolean success = super.remove(targetChild);
274 		if (logger.isLoggable(Level.FINE)) {
275 			logger.fine(this + " removed " + targetChild + " success:"
276 					+ success);
277 		}
278 
279 		// remove from transient refs set
280 		// removeFromTransientRefs(targetChild);
281 
282 		// more to do if the removal is of a pod
283 		if (success && targetChild instanceof Pod) {
284 			Pod pod = (Pod) targetChild;
285 
286 			// pod itself is already removed
287 
288 			// clean out podvar assignments involving that pod
289 			List<Assignment> calls = this.assemblyCalls;
290 			List<Assignment> callsToRemove = new ArrayList<Assignment>();
291 			for (Assignment assignment : calls) {
292 				boolean remove = false;
293 				if (assignment.getLeft().getPodId().equals(pod.getPodId()))
294 					remove = true;
295 				else if (assignment.getRight().getPodId()
296 						.equals(pod.getPodId()))
297 					remove = true;
298 				if (remove) {
299 					callsToRemove.add(assignment);
300 					// also remove from import vars if it's there
301 					String varName = assignment.getLeft().getVarName();
302 					vars.remove(varName);
303 				}
304 			}
305 			this.assemblyCalls.removeAll(callsToRemove);
306 		}
307 
308 		return success;
309 	}
310 
311 	/**
312 	 * @param targetChild
313 	 */
314 	@Deprecated
315 	private void removeFromTransientRefs(Object targetChild) {
316 		for (PropertyRef ref : transientRefs) {
317 			try {
318 				if (targetChild == ref.getCurrentValue()) {
319 					transientRefs.remove(ref);
320 				}
321 			} catch (Exception e) {
322 				// TODO Auto-generated catch block
323 				e.printStackTrace();
324 			}
325 		}
326 	}
327 
328 	private void assembleChildren(PodRegistry registry)
329 			throws UnknownPodException {
330 		for (PodUuid podId : childPodIds) {
331 			Pod pod = registry.getPod(podId);
332 			if (pod == null)
333 				throw new UnknownPodException(podId, registry);
334 			add(pod);
335 			pod.assemble(registry);
336 		}
337 	}
338 
339 	public Set<PodUuid> getChildPodIds() {
340 		refreshChildPodsIdSet();
341 		return childPodIds;
342 	}
343 
344 	/**
345 	 * 
346 	 */
347 	private void refreshChildPodsIdSet() {
348 		Collection<Pod> podChildren = SailBeanUtils.childrenOfType(this,
349 				Pod.class);
350 		Set<PodUuid> idsOfPodChildren = new HashSet<PodUuid>();
351 		for (Pod pod : podChildren) {
352 			idsOfPodChildren.add(pod.getPodId());
353 		}
354 		childPodIds = idsOfPodChildren;
355 	}
356 
357 	/**
358 	 * Used only by the XMLEncoder unmarshaller as a way of restoring references
359 	 * to the pod childen. The actually pod childen become members of this pod
360 	 * collection during Pod.assemble()
361 	 * 
362 	 * @param childPodIds
363 	 */
364 	public void setChildPodIds(Set<PodUuid> childPodIds) {
365 		this.childPodIds = childPodIds;
366 	}
367 
368 	/**
369 	 * Returns whether the value is the same as a podvar that is transient.
370 	 * (i.e. should not be marshalled) Assumes instances of Pod cannot be
371 	 * imported.
372 	 * 
373 	 * @param value
374 	 * @return true iff the value matches the current value of one of the refs
375 	 *         in this pod marked transient
376 	 */
377 	public boolean isTransient(Object value) {
378 		if (value instanceof Pod)
379 			return false;
380 
381 		// since there's no way to compare it, consider a null value to never be
382 		// transient
383 		if (value == null)
384 			return false;
385 
386 		/*
387 		 * If the value is an array, it is transient if any of its elements is
388 		 * transient. So iterate over each and if you find one return true.
389 		 */
390 		Class<? extends Object> valueClass = value.getClass();
391 		if (valueClass.isArray()) {
392 			Object[] values = ((Object[]) value);
393 			for (int i = 0; i < values.length; i++) {
394 				Object elementValue = values[i];
395 				if (valueMatchesReference(elementValue, transientRefs)) {
396 					return true;
397 				}
398 			}
399 		}
400 
401 		if (valueMatchesReference(value, transientRefs)) {
402 			// System.out.println("returning true: some match of " + value);
403 			return true;
404 		}
405 
406 		return false;
407 	}
408 
409 	/**
410 	 * @param value
411 	 * @param propertyReferences
412 	 *            TODO
413 	 * @param valueClass
414 	 */
415 	private static boolean valueMatchesReference(Object value,
416 			Set<PropertyRef> propertyReferences) {
417 		for (PropertyRef ref : propertyReferences) {
418 			try {
419 				if (value == ref.getCurrentValue()) {
420 					return true;
421 				}
422 			} catch (Exception e) {
423 				logger.warning("exception: " + e);
424 			}
425 		}
426 		return false;
427 	}
428 
429 	@Override
430 	public String toString() {
431 		return "Pod@" + podId;
432 	}
433 
434 	// TODO implement this properly with check against BeanContext facilities. a
435 	// unit test even!
436 	@Override
437 	public int hashCode() {
438 		return super.hashCode();
439 		// the code below breaks the BeanContext children facilities
440 		// return new HashCodeBuilder(29, 3).append(podId).append(childPodIds)
441 		// .append(children).append(vars).append(transientRefs).append(
442 		// transientRefs).toHashCode();
443 	}
444 
445 	/**
446 	 * Record that the pod has been modified, which requires that the pod id
447 	 * change
448 	 */
449 	public void touch() {
450 		// FIXME make a more reliable revision system
451 		// FIXME ignoring for now, since we're using curnit-level versioning
452 //		setPodId(BinaryUtils.createNewPodUuidFromOld(getPodId()));
453 	}
454 
455 	/**
456 	 * @return Returns the assembled.
457 	 */
458 	public boolean isAssembled() {
459 		return assembled;
460 	}
461 
462 }