Source code: com/RuntimeCollective/webapps/bean/VersionedExtension.java
1 /* $Header: /home/CVS/rjp/src/com/RuntimeCollective/webapps/bean/VersionedExtension.java,v 1.5 2003/09/30 15:13:10 joe Exp $
2 * $Revision: 1.5 $
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.webapps.RuntimeDataSource;
33 import com.RuntimeCollective.webapps.RuntimeParameters;
34 import com.RuntimeCollective.webapps.EntityBeanStore;
35 import com.RuntimeCollective.webapps.bean.Duplicable;
36 import com.RuntimeCollective.webapps.bean.EntityBean;
37 import com.RuntimeCollective.webapps.bean.Versioned;
38
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.sql.SQLException;
44
45 /**
46 * An extension which can be used to stick the Versioned behaviour to any Duplicable bean.
47 * This also adds a VersionNotes property to each version.
48 *
49 * @version $Id: VersionedExtension.java,v 1.5 2003/09/30 15:13:10 joe Exp $
50 */
51 public class VersionedExtension implements Versioned {
52
53 private static final String SELECT_ID = "select id from ";
54 private static final String WHERE_DUPLICABLE_ID = " where duplicable_id = ";
55 private static final String DELETE_FROM = "delete from ";
56 private static final String WHERE_ID = " where id = ";
57 private static final String SELECT_DATA = "select duplicable_id, version_tag, version_ref, version_notes from ";
58 private static final String SELECT_ID_TAG = "select id, version_tag from ";
59 private static final String WHERE_VER_REF = " where version_ref = ";
60
61 private static final String ESC = "'";
62 private static final String EMPTY_STRING = "";
63 private static final String SPACE = " ";
64
65 public static final String DATABASE_TABLE = "webapps_versionedext";
66 private static final String FIELD_DUPLICABLE_ID = "duplicable_id";
67 private static final String FIELD_VTAG = "version_tag";
68 private static final String FIELD_VREF = "version_ref";
69 private static final String FIELD_VNOTES = "version_notes";
70
71
72
73 // -------------------- EntityBean stuff -------------------------
74
75 protected int id;
76
77 public void setId(int id) {
78 this.id = id;
79 }
80
81 public int getId() {
82 return id;
83 }
84
85 /**
86 * Save this bean in the database.
87 */
88 public void save() {
89
90 // duplicableId shouldn't be null, really
91 try {
92 Integer duplicableInt = (duplicableId == EntityBean.NULL_ID) ? null : new Integer(duplicableId);
93 Integer versionRef = (versionReference == EntityBean.NULL_ID) ? null : new Integer(versionReference);
94 RuntimeDataSource.save(id, DATABASE_TABLE,
95 new String[] { FIELD_DUPLICABLE_ID, FIELD_VTAG, FIELD_VREF, FIELD_VNOTES },
96 new Object[] { duplicableInt, versionTag, versionRef, versionNotes }
97 );
98 } catch (SQLException e) {
99 RuntimeParameters.logError(this, "Could not save.", e);
100 e.printStackTrace();
101 }
102 }
103
104 /**
105 * Delete this bean from the database.
106 */
107 public void delete() {
108 try {
109 RuntimeDataSource.update(DELETE_FROM+DATABASE_TABLE+WHERE_ID+id);
110
111 // refresh the tag list
112 refreshVersionsCache(true);
113
114 } catch (SQLException e) {
115 RuntimeParameters.logError(this, "Could not delete.", e);
116 e.printStackTrace();
117 }
118 }
119
120 /**
121 * Constructs a new blank bean with a unique id.
122 */
123 public VersionedExtension() throws SQLException {
124 setId(RuntimeDataSource.nextId());
125
126 setVersionReference(id);
127 setVersionTag(Versioned.TRUNK_TAG);
128 setVersionNotes("");
129 }
130
131 /**
132 * Gets a bean from the RuntimeDataSource, given an id.
133 *
134 * @param id id of the VersionedExtension bean
135 * @exception SQLException thrown if no bean with such an id exits
136 */
137 public VersionedExtension(int id) throws SQLException {
138
139 Object[] results = RuntimeDataSource.queryRow(SELECT_DATA+DATABASE_TABLE+WHERE_ID+id);
140 if (results.length != 4) {
141 throw new SQLException("Cannot load VersionedExtension with id="+id+" : "
142 +results.length+" fields found in "+DATABASE_TABLE+".");
143 }
144
145 this.id = id;
146
147 if (results[0] != null)
148 duplicableId = Integer.parseInt(results[0].toString());
149 else
150 duplicableId = EntityBean.NULL_ID;
151
152 setVersionTag((String) results[1]);
153
154 if (results[2] != null)
155 setVersionReference(((Integer) results[2]).intValue());
156 else
157 setVersionReference(EntityBean.NULL_ID);
158
159 setVersionNotes((String) results[3]);
160
161 // refresh the tag list
162 refreshVersionsCache(false);
163 }
164
165
166 // -------------------- Versioned stuff -------------------------
167
168 /** This is not implemented - we *could* make VersionedExtension properly Duplicable... */
169 public Duplicable makeDuplicate() {
170 return null;
171 }
172
173 /** This is not implemented - we *could* make VersionedExtension properly Duplicable... */
174 public Duplicable makeDuplicate(int duplicateId) {
175 return null;
176 }
177
178 /** This is not implemented - we *could* make VersionedExtension properly Duplicable... */
179 public Duplicable customiseDuplicate(Duplicable duplicate) {
180 return null;
181 }
182
183
184 // -------------------- Versioned stuff -------------------------
185
186 /**
187 * The Reference of this Version, that is,
188 * the id of the first object in the family of versions.
189 */
190 protected int versionReference;
191
192 public void setVersionReference(int reference) {
193 versionReference = reference;
194 }
195
196 public int getVersionReference() {
197 return versionReference;
198 }
199
200 /* The Tag of this Version */
201 protected String versionTag;
202
203 public void setVersionTag(String tag) {
204 versionTag = tag;
205 }
206
207 public String getVersionTag() {
208 return versionTag;
209 }
210
211 /* The Notes of this Version */
212 protected String versionNotes;
213
214 public void setVersionNotes(String notes) {
215 versionNotes = notes;
216 }
217
218 public String getVersionNotes() {
219 return versionNotes;
220 }
221
222 /**
223 * Get the List of all version tags for this VersionedExtension,
224 * ie for this Duplicable.
225 */
226 public List getVersionTagList() {
227 List result = new ArrayList();
228
229 Iterator ids = versionsCache.keySet().iterator();
230 Integer anId;
231 String aTag;
232 while (ids.hasNext()) {
233 anId = (Integer) ids.next();
234 aTag = (String) versionsCache.get(anId);
235 if (!result.contains(aTag)) {
236 result.add(aTag);
237 }
238 }
239
240 return result;
241 }
242
243 /**
244 * Get the List of all versions, in unspecified order.
245 * FIXME: this should be trickled up to the Versioned interface.
246 */
247 public List getVersionList() {
248 Iterator ids = versionsCache.keySet().iterator();
249 List versions = new ArrayList();
250
251 while (ids.hasNext()) {
252 versions.add(RuntimeParameters.getStore().get(VersionedExtension.class.getName(), ((Integer) ids.next()).intValue()));
253 }
254
255 return versions;
256 }
257
258
259 /**
260 * Create and save a new version of this object based on a existing one,
261 * and tag it with the specified tag. If a version with the newVersionTag
262 * already exists, it will be overwritten - ie its id will be reused.
263 * <p>
264 * The reference version should already exist, otherwise this method will do
265 * nothing and return "null".
266 * <br>
267 * A version with the newVersionTag may or may not exist: if it doesn't, it will
268 * be created - if it does, it will be overwritten.
269 * <p>
270 * Note the new/updated Duplicable and VersionedExtension are both saved.
271 *
272 * @param referenceVersionTag, the version which will be duplicated and used as
273 * a base for the new version
274 * @param newVersionTag, the tag given to the new version
275 * @return a new version, or null if either the reference version doesn't exist, or
276 * the new tag is already used.
277 */
278 public Versioned createOrReplaceVersion(String referenceVersionTag, String newVersionTag) {
279
280 RuntimeParameters.logDebug(this, "Versioning "+getDuplicable().getClass().getName()
281 +" "+getDuplicable().getId()+" from "+referenceVersionTag+" to "+newVersionTag);
282
283 // get the VersionReference
284 // (we need to do this first as we may be overwriting the Live version)
285 int versionRef = getVersion(Versioned.TRUNK_TAG).getId();
286
287 VersionedExtension edited = (VersionedExtension) getVersion(referenceVersionTag);
288 if (edited == null) {
289 RuntimeParameters.logDebug(this, "Could not find the referenceVersion for Tag "+referenceVersionTag);
290 return null;
291 }
292
293 Duplicable editedDuplicable = edited.getDuplicable();
294
295 VersionedExtension newVersion = (VersionedExtension) getVersion(newVersionTag);
296 Duplicable newVersionDuplicable;
297 if (newVersion == null) {
298 // create new Version
299 newVersion = (VersionedExtension) RuntimeParameters.getStore().create(VersionedExtension.class.getName());
300 newVersionDuplicable = editedDuplicable.makeDuplicate();
301
302 } else {
303 // overwrite that Version
304 newVersionDuplicable = newVersion.getDuplicable();
305 if (newVersionDuplicable != null) {
306 newVersionDuplicable = editedDuplicable.makeDuplicate(newVersionDuplicable.getId());
307 } else {
308 RuntimeParameters.logError(this, "Trying to overwrite an existing VersionedExtension with a null Duplicable.");
309 return null;
310 }
311 }
312
313 // populate/update the extension of the new/updated duplicable
314 newVersion.setDuplicable(newVersionDuplicable);
315 newVersion.setVersionTag(newVersionTag);
316 newVersion.setVersionReference(versionRef);
317
318 // save and refresh the new version from the db, to prevent stale data
319 RuntimeParameters.getStore().save(newVersionDuplicable);
320 RuntimeParameters.getStore().save(newVersion);
321 RuntimeParameters.getStore().refresh(newVersion);
322
323 // refresh the tag list
324 refreshVersionsCache(true);
325
326 return newVersion;
327 }
328
329 /**
330 * Get an existing version of this object.
331 * <p>
332 * The version should already exist. It is doesn't, then this method
333 * will do nothing and return "null".
334 *
335 * @param aVersionTag, the version required
336 * @return the required version, or "null" if it doesn't exist
337 */
338 public Versioned getVersion(String aVersionTag) {
339
340 //System.out.println(" This "+getId()+" ("+VersionTag+"), looking for tag : "+versionTag);
341
342 // quick optimisation
343 if ((aVersionTag != null) && (aVersionTag.equals(versionTag))) {
344 return this;
345 }
346
347 Iterator ids = versionsCache.keySet().iterator();
348 Integer anId;
349 String aTag;
350 while (ids.hasNext()) {
351 anId = (Integer) ids.next();
352 aTag = (String) versionsCache.get(anId);
353
354 //System.out.println(" Versions : id : "+anId+" tag : "+aTag);
355
356 if (aTag.equals(aVersionTag)) {
357 try {
358 return (Versioned) RuntimeParameters.getStore().get(Versioned.class.getName(), anId.intValue());
359 } catch (RuntimeException e) {
360 RuntimeParameters.logError(this, "Couldn't get version "+aVersionTag+" for "+getId(), e);
361 e.printStackTrace();
362 return null;
363 }
364 }
365 }
366
367 return null;
368 }
369
370 /**
371 * A HashMap of all version ids for this object, and their version tag.
372 */
373 protected HashMap versionsCache = new HashMap();
374
375 protected void setVersionsCache(HashMap map) {
376 versionsCache = map;
377 }
378
379 /** Refresh the cache of VersionTags, for this object and all its versions
380 * @param allVersions, whether all versions should be refreshed, or just this one
381 */
382 protected void refreshVersionsCache(boolean allVersions) {
383 HashMap versions = readVersionsCache();
384
385 //RuntimeParameters.logDebug(this, "Refreshing version tag list "+allVersions);
386
387 // we need to do that even if we do allVersions, as we are going to use
388 // this object's map to getVersion(aTag)
389 setVersionsCache(versions);
390
391 if (allVersions) {
392 VersionedExtension version;
393 Iterator ids = versions.keySet().iterator();
394 Integer anId;
395 String aTag;
396 while (ids.hasNext()) {
397 anId = (Integer) ids.next();
398 aTag = (String) versions.get(anId);
399 version = (VersionedExtension) getVersion(aTag);
400 version.setVersionsCache(versions);
401 }
402 }
403 }
404
405 /** Read from the database the list of VersionTags which exist
406 * for that VersionedExtension, that is for that Duplicable, really.
407 */
408 protected HashMap readVersionsCache() {
409
410 HashMap result = new HashMap();
411
412 try {
413 // load the VersionTags
414 Object[][] rows = RuntimeDataSource.queryRows((new StringBuffer(80)).append(SELECT_ID_TAG).append(DATABASE_TABLE).append(WHERE_VER_REF).append(getVersionReference()).toString());
415
416 if (rows != null) {
417 for (int i=0; i<rows.length; i++) {
418 result.put((Integer) rows[i][0], (String) rows[i][1]);
419 //System.out.println(" Found version "+rows[i][1]+" for id "+rows[i][0]);
420 }
421 }
422
423 } catch (Exception e) {
424 RuntimeParameters.logError(this, "Could not getVersionTagList().", e);
425 e.printStackTrace();
426 }
427
428 return result;
429 }
430
431
432 // -------------------- VersionedExtension stuff -------------------------
433
434 /** The id of the Duplicable this VersionedExtension is for. */
435 protected int duplicableId = EntityBean.NULL_ID;
436
437 /** Gets the duplicable. */
438 public Duplicable getDuplicable() {
439 return (Duplicable) RuntimeParameters.getStore().get(Duplicable.class.getName(), duplicableId);
440 }
441
442 /** Sets the duplicable. */
443 public void setDuplicable(Duplicable duplicable) {
444 if (duplicable == null)
445 this.duplicableId = Duplicable.NULL_ID;
446 else
447 this.duplicableId = duplicable.getId();
448 }
449
450 public String toString() {
451 return VersionedExtension.class.getName() + " " + id + " ( " + super.toString() + " )";
452 }
453
454
455 // -------------------- VersionedExtension statics -------------------------
456
457 /**
458 * Gets the VersionedExtension for a particular duplicable, if one exists.
459 */
460 public static VersionedExtension getFor(Duplicable duplicable) {
461
462 if (duplicable == null) {
463 return null;
464 }
465
466 try {
467 int[] ids = RuntimeDataSource.queryInts(SELECT_ID+DATABASE_TABLE+WHERE_DUPLICABLE_ID+duplicable.getId());
468 if (ids.length > 0) {
469 return (VersionedExtension) RuntimeParameters.getStore().get(VersionedExtension.class.getName(), ids[0]);
470 }
471
472 } catch (Exception e) {
473 RuntimeParameters.logError("VersionedExtension", "Could not getFor.", e);
474 e.printStackTrace();
475 }
476
477 return null;
478 }
479
480 /**
481 * Gets the VersionedExtension for a particular bean,
482 * or create a TRUNK_TAG VersionedExtension if there is none.
483 * <p>
484 * NOTE: newly created VersionedExtension objects are NOT saved -
485 * you'll need to do that.
486 */
487 public static VersionedExtension getOrCreateFor(Duplicable duplicable) {
488
489 if (duplicable == null)
490 return null;
491
492 VersionedExtension verExt = VersionedExtension.getFor(duplicable);
493 if (verExt != null) {
494 return verExt;
495 }
496
497 // ok we'll need to create it
498 verExt = (VersionedExtension) RuntimeParameters.getStore().create(VersionedExtension.class.getName());
499 verExt.populateAsTrunkOf(duplicable);
500 // not saving it - caller is responsible!
501 return verExt;
502 }
503
504 /**
505 * This method, not often used, populates the Extension
506 * like it is the TRUNK of the given Duplicable.
507 * This is called by getOrCreateFor when creating.
508 */
509 public void populateAsTrunkOf(Duplicable duplicable) {
510 setDuplicable(duplicable);
511 setVersionTag(Versioned.TRUNK_TAG);
512 setVersionReference(getId());
513 // setup the versionCache without reading the db (we may not be saved)
514 versionsCache = new HashMap();
515 versionsCache.put(new Integer(getId()), getVersionTag());
516 }
517 }
518
519
520
521