Source code: com/RuntimeCollective/webapps/bean/TreeExtension.java
1 /* $Header: /home/CVS/rjp/src/com/RuntimeCollective/webapps/bean/TreeExtension.java,v 1.7 2003/09/30 15:13:10 joe Exp $
2 * $Revision: 1.7 $
3 * $Date: 2003/09/30 15:13:10 $
4 *
5 * ====================================================================
6 *
7 * Josephine : http://www.runtime-collective.com/josephine/index.html
8 *
9 * Copyright (C) 2003 Runtime Collective
10 *
11 * This product includes software developed by the
12 * Apache Software Foundation (http://www.apache.org/).
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2.1 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 *
28 */
29
30 package com.RuntimeCollective.webapps.bean;
31
32 import com.RuntimeCollective.permission.bean.PermissibleExtension;
33 import com.RuntimeCollective.webapps.BeanUtils;
34 import com.RuntimeCollective.webapps.EntityBeanStore;
35 import com.RuntimeCollective.webapps.RuntimeDataSource;
36 import com.RuntimeCollective.webapps.RuntimeParameters;
37 import com.RuntimeCollective.webapps.bean.EntityBean;
38 import com.RuntimeCollective.webapps.bean.User;
39
40 import java.net.MalformedURLException;
41 import java.sql.SQLException;
42 import java.util.ArrayList;
43 import java.util.List;
44 import javax.servlet.jsp.PageContext;
45
46 import org.apache.commons.beanutils.PropertyUtils;
47 import org.apache.struts.util.RequestUtils;
48
49 /**
50 * This is an extension which places the extended EntityBean into a tree,
51 * thus having 0/1 parent and 0/n children. The type of parents and children
52 * is unconstrained (EntityBeans, that is).
53 * <p>
54 * A given EntityBean can belong to any number of trees, it will have
55 * 1/n TreeExtension(s) for each tree it belongs to.
56 *
57 * @version $Id: TreeExtension.java,v 1.7 2003/09/30 15:13:10 joe Exp $
58 */
59 public class TreeExtension implements EntityBean {
60
61 private static final String SELECT_ID = "select id from ";
62 private static final String SELECT_TREE_NAME = "select tree_name from ";
63 private static final String WHERE_BEAN_ID = " where bean_id = ";
64 private static final String AND_TREE_NAME = " and tree_name = '";
65 private static final String DELETE_FROM = "delete from ";
66 private static final String WHERE_ID = " where id = ";
67 private static final String SELECT_DATA = "select bean_id, tree_name, parent_id from ";
68 private static final String UPDATE = "update ";
69 private static final String SET_POSITION = " set pos_no = ";
70 private static final String AND_PARENT = ", parent_id = ";
71 private static final String NULL_PARENT_WHERE = " set parent_id = null where parent_id = ";
72 private static final String WHERE_PARENT = " where parent_id = ";
73 private static final String ORDER_POSITION = " order by pos_no";
74 private static final String ORDER_BY_TREE_NAME = " order by tree_name";
75
76 private static final String ESC = "'";
77 private static final String EMPTY_STRING = "";
78 private static final String SPACE = " ";
79
80 public static final String DATABASE_TABLE = "webapps_treeext";
81 private static final String FIELD_TREE_NAME = "tree_name";
82 private static final String FIELD_BEAN_ID = "bean_id";
83 private static final String FIELD_PARENT_ID = "parent_id";
84 private static final String FIELD_POSITION = "pos_no";
85
86 public static final String START_HREF = "<a href=\"";
87 public static final String START_SPAN = "<span class=\"";
88 public static final String END_QUOTE_TAG = "\">";
89 public static final String END_HREF = "</a>";
90 public static final String END_SPAN = "</span>";
91 public static final String UNKNOWN = "unknown";
92 public static final String SEPARATOR = " : ";
93
94
95 // -------------------- EntityBean implementation -------------------------
96
97 /** This bean's id */
98 protected int id;
99
100 /**
101 * Set the unique id of this bean instance.
102 */
103 public void setId(int id) {
104 this.id = id;
105 }
106
107 /**
108 * Get the unique id of this bean instance.
109 */
110 public int getId() {
111 return id;
112 }
113
114 /**
115 * Save this bean in the database.
116 */
117 public void save() {
118
119 // parent_id and pos_no are really owned by the parent
120
121 try {
122
123 // beanId shouldn't be null, really
124 Integer beanInt = (beanId == EntityBean.NULL_ID) ? null : new Integer(beanId);
125 Integer parentInt = (parentId == EntityBean.NULL_ID) ? null : new Integer(parentId);
126 RuntimeDataSource.save(id, DATABASE_TABLE,
127 new String[] { FIELD_BEAN_ID, FIELD_TREE_NAME, FIELD_PARENT_ID },
128 new Object[] { beanInt,
129 treeName,
130 parentInt }
131 );
132
133 // save the parent-child mappings (parent_id and pos_no)
134 int childId;
135 ArrayList updates = new ArrayList(childrenIds.size());
136 for (int i=0; i<childrenIds.size(); i++) {
137 childId = ((Integer) childrenIds.get(i)).intValue();
138 updates.add((new StringBuffer(90)).append(UPDATE).append(DATABASE_TABLE).append(SET_POSITION).append(i).append(AND_PARENT).append(id).append(WHERE_ID).append(childId).toString());
139 }
140 if (!updates.isEmpty()) {
141 String[] updates_array = new String[updates.size()];
142 updates_array = (String[]) updates.toArray(updates_array);
143 RuntimeDataSource.update(updates_array);
144 }
145 } catch (SQLException e) {
146 RuntimeParameters.logError(this, "Could not save.", e);
147 e.printStackTrace();
148 }
149 }
150
151 /**
152 * Delete this bean from the database.
153 */
154 public void delete() {
155 try {
156 RuntimeDataSource.update(new String[] {
157 UPDATE+DATABASE_TABLE+NULL_PARENT_WHERE+id,
158 DELETE_FROM+DATABASE_TABLE+WHERE_ID+id
159 } );
160 } catch (SQLException e) {
161 RuntimeParameters.logError(this, "Could not delete.", e);
162 e.printStackTrace();
163 }
164 }
165
166
167 // -------------------- TreeExtension specific -------------------------
168
169 /**
170 * Constructs a new blank bean with a unique id.
171 */
172 public TreeExtension() throws SQLException {
173 setId(RuntimeDataSource.nextId());
174 }
175
176 /**
177 * Gets a bean from the RuntimeDataSource, given an id.
178 *
179 * @param id id of the Moderated bean
180 * @exception SQLException thrown if no bean with such an id exits
181 */
182 public TreeExtension(int id) throws SQLException {
183 Object[] results = RuntimeDataSource.queryRow(SELECT_DATA+DATABASE_TABLE+WHERE_ID+id);
184 if (results.length != 3) {
185 throw new SQLException("Cannot load TreeExtension with id="+id+" : "
186 +results.length+" fields found in "+DATABASE_TABLE+".");
187 }
188
189 this.id = id;
190
191 if (results[0] != null)
192 beanId = Integer.parseInt(results[0].toString());
193 else
194 beanId = EntityBean.NULL_ID;
195
196 treeName = (String) results[1];
197
198 if (results[2] != null)
199 parentId = Integer.parseInt(results[2].toString());
200 else
201 parentId = EntityBean.NULL_ID;
202
203 // load the children mappings
204 int[] children = RuntimeDataSource.queryInts(SELECT_ID+DATABASE_TABLE+WHERE_PARENT+id+ORDER_POSITION);
205 for (int i=0; i<children.length; i++) {
206 childrenIds.add(new Integer(children[i]));
207 }
208 }
209
210
211 /** The id of the EntityBean this TreeExtension is for. */
212 protected int beanId = EntityBean.NULL_ID;
213
214 /** Gets the bean. */
215 public EntityBean getEntityBean() {
216 return (EntityBean) RuntimeParameters.getStore().get(EntityBean.class.getName(), beanId);
217 }
218
219 /** Sets the bean. */
220 public void setEntityBean(EntityBean bean) {
221 if (bean == null)
222 this.beanId = EntityBean.NULL_ID;
223 else
224 this.beanId = bean.getId();
225 }
226
227 /** The name of the tree we are in. */
228 protected String treeName = null;
229
230 /** Gets the tree name. */
231 public String getTreeName() {
232 return treeName;
233 }
234
235 /** Sets the tree name. */
236 public void setTreeName(String approve) {
237 treeName = approve;
238 }
239
240
241 /** The Parent */
242 protected int parentId = EntityBean.NULL_ID;
243
244 /** Set the Parent - don't use this unless you really mean to; use parent.addChild() instead. */
245 public void setParent(TreeExtension parent) {
246 if (parent != null)
247 parentId = parent.getId();
248 else
249 parentId = EntityBean.NULL_ID;
250 }
251
252 /** Get the Parent */
253 public TreeExtension getParent() {
254 if (parentId != EntityBean.NULL_ID) {
255 return (TreeExtension) RuntimeParameters.getStore().get(TreeExtension.class.getName(), parentId);
256 } else {
257 return null;
258 }
259 }
260
261 /** Get the Parent at the top of the tree, recursively. */
262 public TreeExtension getTopParent() {
263 TreeExtension parent = getParent();
264 if (parent != null) {
265 return parent.getTopParent();
266 } else {
267 return this;
268 }
269 }
270
271
272 /** The Children */
273 protected List childrenIds = new ArrayList();
274
275 /** Remove a Child at a given position. */
276 public void removeChild(int position) {
277 if ((position >= 0) && (position < childrenIds.size())) {
278 childrenIds.remove(position);
279 }
280 }
281
282 /** Remove a Child from this TreeExtension (all its positions, if more than one). */
283 public void removeChild(TreeExtension child) {
284 if (child != null) {
285 Integer childId = new Integer(child.getId());
286 while (childrenIds.contains(childId)) {
287 childrenIds.remove(childId);
288 }
289 child.setParent(null);
290 }
291 }
292
293 /** Add a Child to this TreeExtension. */
294 public void addChild(TreeExtension child) {
295 if (child != null) {
296 Integer childId = new Integer(child.getId());
297 childrenIds.add(childId);
298 child.setParent(this);
299 }
300 }
301
302
303 /** Get the position-nth child from this TreeExtension. */
304 public TreeExtension getChild(int position) {
305 if ((position >= 0) && (position < childrenIds.size())) {
306 return (TreeExtension) RuntimeParameters.getStore().get(TreeExtension.class.getName(), ((Integer) childrenIds.get(position)).intValue());
307 } else {
308 return null;
309 }
310 }
311
312 /** Get the list of children (a List of TreeExtension). */
313 public List getChildren() {
314 return BeanUtils.beansFromIds(childrenIds);
315 }
316
317 /** Get the position, by calling the Parent's getChildPosition. Returns -1 by default (ie no parent). */
318 public int getPosition() {
319 TreeExtension parent = getParent();
320 if (parent != null) {
321 return parent.getChildPosition(this);
322 } else {
323 return -1;
324 }
325 }
326
327 /** Get the position of a child. Returns -1 by default (ie not there). */
328 public int getChildPosition(TreeExtension child) {
329 List children = getChildren();
330 return children.indexOf(child);
331 }
332
333
334 public String toString() {
335 return TreeExtension.class.getName() + " " + id + " ( " + super.toString() + " )";
336 }
337
338 /** Construct a breadcrumb for this TreeExtension. */
339 public String getBreadcrumb(PageContext pageContext, String displayProperty, String displayPath, boolean asLinks, String styleClass) {
340
341 // build a list of all parents
342 List parents = new ArrayList();
343 TreeExtension current = this;
344 while ((current = current.getParent()) != null) {
345 parents.add(current);
346 }
347
348 // go through the list of parents, starting from the root
349 StringBuffer result = new StringBuffer(parents.size()*80);
350 for (int i=parents.size()-1; i>=0; i--) {
351 current = (TreeExtension) parents.get(i);
352 result.append(current.displayInBreadCrumb(pageContext, displayProperty, displayPath, asLinks, styleClass, false));
353 }
354
355 // append the current object
356 result.append(displayInBreadCrumb(pageContext, displayProperty, displayPath, false, styleClass, true));
357
358 return result.toString();
359 }
360
361 /** Render the TreeExtension as one element in a Breadcrumb. */
362 public String displayInBreadCrumb(PageContext pageContext, String displayProperty, String displayPath, boolean asLinks, String styleClass, boolean isLast) {
363
364 StringBuffer result = new StringBuffer(80);
365 EntityBean bean = getEntityBean();
366
367 if (asLinks) {
368 result.append(START_HREF);
369 try {
370 result.append(RequestUtils.computeURL(pageContext, null, null, displayPath+getId(), null, null, false));
371 } catch (MalformedURLException e) {
372 RuntimeParameters.logError(this, "Couldn't compute link URL.", e);
373 }
374 result.append(END_QUOTE_TAG);
375 }
376
377 if (styleClass != null) {
378 result.append(START_SPAN)
379 .append(styleClass)
380 .append(END_QUOTE_TAG);
381 }
382
383 Object displayValue = null;
384 try {
385 displayValue = PropertyUtils.getProperty(bean, displayProperty);
386 } catch (Exception e) {
387 RuntimeParameters.logError(this, "Couldn't get display value.", e);
388 }
389 if (displayValue == null) {
390 result.append(UNKNOWN);
391 } else {
392 result.append(displayValue.toString());
393 }
394
395 if (styleClass != null) {
396 result.append(END_SPAN);
397 }
398
399 if (asLinks) {
400 result.append(END_HREF);
401 }
402
403 if (!isLast) {
404 result.append(SEPARATOR);
405 }
406
407 return result.toString();
408 }
409
410 /**
411 * Recursive Permission test, which will check the PermissibleExtension
412 * of the EntityBean of this TreeExtension, and then (if necessary)
413 * do the same on the parent(s).
414 *
415 * @param action, the action to be performed
416 * @param user, the user who is trying to perform the action
417 * @return a boolean, yes the user can perform the action, or no
418 */
419 public boolean canPerformAction(String action, User user) {
420
421 RuntimeParameters.logDebug(this, "Checking permissions on tree extension "+getId()+" for action "+action+" for user "+(user != null ? ""+user.getId() : "null"));
422
423 // try to check this bean
424 EntityBean bean = getEntityBean();
425 if (bean != null) {
426 PermissibleExtension permissible = PermissibleExtension.getFor(bean);
427 if (permissible != null) {
428 if (!permissible.canPerformAction(action, user)) {
429 return false;
430 }
431 }
432 }
433
434 // call the parent, if any
435 TreeExtension parent = getParent();
436 if (parent != null) {
437 return parent.canPerformAction(action, user);
438 }
439
440 // all fine, no more checks to do
441 return true;
442 }
443
444
445 // -------------------- TreeExtension statics -------------------------
446
447 /**
448 * Gets the TreeExtension for a particular bean and a particular treeName, if one exists.
449 * If more than one exists, the firstly created on (ie smallest id) will be returned.
450 */
451 public static TreeExtension getFor(EntityBean bean, String treeName) {
452 List all = getAllFor(bean, treeName);
453 if ((all != null) && (!all.isEmpty())) {
454 return (TreeExtension) all.get(0);
455 } else {
456 return null;
457 }
458 }
459
460 /**
461 * Gets all the TreeExtensions for a particular bean and a particular treeName, ordered by id.
462 */
463 public static List getAllFor(EntityBean bean, String treeName) {
464
465 if ((bean == null) || (treeName == null)) {
466 return null;
467 }
468
469 List result = null;
470
471 try {
472 int[] ids = RuntimeDataSource.queryInts(SELECT_ID+DATABASE_TABLE+WHERE_BEAN_ID+bean.getId()+AND_TREE_NAME+RuntimeDataSource.escape(treeName)+ESC);
473 result = new ArrayList(ids.length);
474 for (int i=0; i<ids.length; i++) {
475 result.add((TreeExtension) RuntimeParameters.getStore().get(TreeExtension.class.getName(), ids[i]));
476 }
477 } catch (SQLException e) {
478 RuntimeParameters.logError("TreeExtension", "Could not getAllFor.", e);
479 e.printStackTrace();
480 }
481
482 return result;
483 }
484
485 /**
486 * Gets all the TreeExtensions for a particular bean, ordered by treeName then by id.
487 */
488 public static List getAllFor(EntityBean bean) {
489
490 List result = new ArrayList();
491
492 List treeNames = TreeExtension.getTreeNamesFor(bean);
493 for (int i=0; i<treeNames.size(); i++) {
494 result.addAll(getAllFor(bean, (String) treeNames.get(i)));
495 }
496
497 return result;
498 }
499
500
501
502 /**
503 * Get a list of TreeNames to which an EntityBean belongs, order alphabetically.
504 */
505 public static List getTreeNamesFor(EntityBean bean) {
506
507 if (bean == null) {
508 return null;
509 }
510
511 List result = null;
512
513 try {
514 Object[][] names = RuntimeDataSource.queryRows(SELECT_TREE_NAME+DATABASE_TABLE+WHERE_BEAN_ID+bean.getId()+ORDER_BY_TREE_NAME);
515 String aName;
516 result = new ArrayList(names.length);
517 for (int i=0; i<names.length; i++) {
518 aName = (String) names[i][0];
519 if (!result.contains(aName)) {
520 result.add(aName);
521 }
522 }
523 } catch (SQLException e) {
524 RuntimeParameters.logError("TreeExtension", "Could not getTreeNamesFor.", e);
525 e.printStackTrace();
526 }
527
528 return result;
529 }
530
531 /**
532 * Check if the EntityBean belongs, and *only* belongs, to the TreeName provided.
533 */
534 public static boolean onlyBelongsTo(EntityBean bean, String treeName) {
535
536 if ((bean == null) || (treeName == null)) {
537 return false;
538 }
539
540 List names = getTreeNamesFor(bean);
541
542 RuntimeParameters.logDebug("TreeExtension", "Tree name : "+treeName+", size : "+names);
543 //RuntimeParameters.logDebug("TreeExtension", "Tree name : "+treeName+", size : "+names.size()+", first : "+names.get(0));
544
545 return ((names != null) && (names.size() == 1) && (treeName.equals((String) names.get(0))));
546 }
547 }
548
549
550