001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.shared.content;
034:
035: import com.flexive.shared.exceptions.FxInvalidParameterException;
036: import com.flexive.shared.exceptions.FxNotFoundException;
037: import com.flexive.shared.structure.FxPropertyAssignment;
038: import com.flexive.shared.value.FxValue;
039: import org.apache.commons.lang.ArrayUtils;
040:
041: import java.io.Serializable;
042: import java.util.ArrayList;
043: import java.util.Collections;
044: import java.util.List;
045:
046: /**
047: * Compute Delta changes for FxContents
048: *
049: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
050: */
051: public class FxDelta implements Serializable {
052: private static final long serialVersionUID = -6246822483703676822L;
053:
054: /**
055: * A single delta change
056: */
057: public static class FxDeltaChange implements Serializable {
058: private static final long serialVersionUID = -9026076539541708345L;
059:
060: private String XPath;
061: private FxData originalData;
062: private FxData newData;
063: private boolean positionChange;
064: private boolean dataChange;
065: private boolean property;
066: private boolean languageSettingChanged;
067: private int retryCount;
068: private boolean multiColumn;
069:
070: /**
071: * Ctor
072: *
073: * @param XPath affected XPath
074: * @param originalData original data
075: * @param newData new data
076: */
077: @SuppressWarnings({"ConstantConditions"})
078: public FxDeltaChange(String XPath, FxData originalData,
079: FxData newData) {
080: this .XPath = XPath;
081: this .originalData = originalData;
082: this .newData = newData;
083: this .property = originalData instanceof FxPropertyData
084: || newData instanceof FxPropertyData;
085: if (newData == null || originalData == null) {
086: this .positionChange = true;
087: this .dataChange = true;
088: } else {
089: positionChange = newData.getPos() != originalData
090: .getPos();
091: this .dataChange = property
092: && !((FxPropertyData) originalData).getValue()
093: .equals(
094: ((FxPropertyData) newData)
095: .getValue());
096: }
097: this .languageSettingChanged = this .dataChange
098: && this .property;
099: if (this .languageSettingChanged
100: && this .originalData != null
101: && this .newData != null) {
102: FxValue ov = ((FxPropertyData) originalData).getValue();
103: FxValue nv = ((FxPropertyData) newData).getValue();
104: this .languageSettingChanged = !ArrayUtils.isEquals(ov
105: .getTranslatedLanguages(), nv
106: .getTranslatedLanguages());
107: }
108: if (this .property && this .newData != null) {
109: switch (((FxPropertyAssignment) ((FxPropertyData) newData)
110: .getAssignment()).getProperty().getDataType()) {
111: case SelectMany:
112: this .multiColumn = true;
113: break;
114: default:
115: this .multiColumn = false;
116: }
117: } else
118: this .multiColumn = false;
119: }
120:
121: /**
122: * Getter for the XPath (based on the origina XPath)
123: *
124: * @return XPath
125: */
126: public String getXPath() {
127: return XPath;
128: }
129:
130: /**
131: * Getter for the original data
132: *
133: * @return original data
134: */
135: public FxData getOriginalData() {
136: return originalData;
137: }
138:
139: /**
140: * Getter for the new data
141: *
142: * @return new data
143: */
144: public FxData getNewData() {
145: return newData;
146: }
147:
148: /**
149: * Is the change affecting a property?
150: *
151: * @return property affected?
152: */
153: public boolean isProperty() {
154: return property;
155: }
156:
157: /**
158: * Is the change affecting a group?
159: *
160: * @return group affected?
161: */
162: public boolean isGroup() {
163: return !property;
164: }
165:
166: /**
167: * Did the position change?
168: *
169: * @return position change
170: */
171: public boolean isPositionChange() {
172: return positionChange;
173: }
174:
175: /**
176: * Has data changed?
177: *
178: * @return data changed
179: */
180: public boolean isDataChange() {
181: return dataChange;
182: }
183:
184: /**
185: * <b>Internal use only!</b>
186: * Is this delta updateable or do we need delete/insert?
187: * A property is not updateable if language settings changed or it spans multiple columns like a select-many
188: *
189: * @return if this chance is updateable
190: */
191: public boolean _isUpdateable() {
192: return !(languageSettingChanged || multiColumn);
193: }
194:
195: /**
196: * <b>Internal use only!</b>
197: * Increase the retry count - used when saving and conflicts arise that
198: * are fixed after some retries (ie positioning changes)
199: */
200: public synchronized void _increaseRetries() {
201: retryCount++;
202: }
203:
204: /**
205: * <b>Internal use only!</b>
206: * Getter for the retry count
207: *
208: * @return retry count
209: */
210: public synchronized int _getRetryCount() {
211: return retryCount;
212: }
213: }
214:
215: private final static List<FxDeltaChange> EMPTY = Collections
216: .unmodifiableList(new ArrayList<FxDeltaChange>(0));
217:
218: private List<FxDeltaChange> updates, adds, removes;
219: private boolean internalPropertyChanged = false;
220:
221: /**
222: * Ctor
223: *
224: * @param updates delta updates
225: * @param adds delta adds
226: * @param removes delta removes
227: */
228: public FxDelta(List<FxDeltaChange> updates,
229: List<FxDeltaChange> adds, List<FxDeltaChange> removes) {
230: this .updates = (updates != null ? updates : EMPTY);
231: this .adds = (adds != null ? adds : EMPTY);
232: this .removes = (removes != null ? removes : EMPTY);
233: checkInternal(this .updates);
234: if (!internalPropertyChanged)
235: checkInternal(this .adds);
236: if (!internalPropertyChanged)
237: checkInternal(this .removes);
238: }
239:
240: private void checkInternal(List<FxDeltaChange> list) {
241: for (FxDeltaChange c : list)
242: if (c.isProperty()
243: && ((c.getOriginalData() != null && c
244: .getOriginalData().isSystemInternal()) || (c
245: .getNewData() != null && c.getNewData()
246: .isSystemInternal()))) {
247: internalPropertyChanged = true;
248: break;
249: }
250: }
251:
252: /**
253: * Get a list of all updated deltas
254: *
255: * @return list of all updated deltas
256: */
257: public List<FxDeltaChange> getUpdates() {
258: return updates;
259: }
260:
261: /**
262: * Get a list of all added deltas
263: *
264: * @return list of all added deltas
265: */
266: public List<FxDeltaChange> getAdds() {
267: return adds;
268: }
269:
270: /**
271: * Get a list of all removed deltas
272: *
273: * @return list of all removed deltas
274: */
275: public List<FxDeltaChange> getRemoves() {
276: return removes;
277: }
278:
279: /**
280: * Was there a change of system internal properties like ACL?
281: *
282: * @return change of system internal properies
283: */
284: public boolean isInternalPropertyChanged() {
285: return internalPropertyChanged;
286: }
287:
288: /**
289: * Are there any changes?
290: *
291: * @return changes?
292: */
293: public boolean changes() {
294: return !(updates.size() == 0 && removes.size() == 0 && adds
295: .size() == 0);
296: }
297:
298: /**
299: * Create a dump of all changes for debugging purposes
300: *
301: * @return dump of all changes for debugging purposes
302: */
303: public String dump() {
304: if (!changes())
305: return "===> No changes! <===";
306: StringBuilder sb = new StringBuilder(1000);
307: sb.append("<=== changes start ===>\n");
308: if (updates.size() > 0) {
309: sb.append("Updates:\n");
310: for (FxDeltaChange c : updates) {
311: if (c.isPositionChange() && !c.isDataChange()) {
312: sb.append(c.getXPath()).append(
313: ": Position changed from ").append(
314: c.getOriginalData().getPos())
315: .append(" to ").append(
316: c.getNewData().getPos()).append(
317: "\n");
318: } else if (c.isDataChange()) {
319: sb.append(c.getXPath()).append(": ");
320: if (c.isPositionChange())
321: sb.append("Position changed from ").append(
322: c.getOriginalData().getPos()).append(
323: " to ").append(c.getNewData().getPos());
324: if (c.isDataChange())
325: sb.append(" [data changes]");
326: if (c._isUpdateable())
327: sb.append(" [lang.settings changes]");
328: sb.append("\n");
329: }
330: }
331: }
332: if (adds.size() > 0) {
333: sb.append("Adds:\n");
334: for (FxDeltaChange c : adds) {
335: sb.append(c.getXPath()).append(": added at position ")
336: .append(c.getNewData().getPos()).append("\n");
337: }
338: }
339: if (removes.size() > 0) {
340: sb.append("Removes:\n");
341: for (FxDeltaChange c : removes) {
342: sb.append(c.getXPath())
343: .append(": removed at position ").append(
344: c.getOriginalData().getPos()).append(
345: "\n");
346: }
347: }
348: sb.append("<=== changes end ===>\n");
349:
350: return sb.toString();
351: }
352:
353: /**
354: * Compare <code>original</code> to <code>compare</code> FxContent.
355: * Both contents should be compacted and empty entries removed for correct results.
356: *
357: * @param original original content
358: * @param compare content that original is compared against
359: * @return deltas
360: * @throws FxInvalidParameterException on errors
361: * @throws FxNotFoundException on errors
362: */
363: public static FxDelta processDelta(final FxContent original,
364: final FxContent compare) throws FxNotFoundException,
365: FxInvalidParameterException {
366: List<String> org = original.getAllXPaths("/");
367: List<String> comp = compare.getAllXPaths("/");
368:
369: List<FxDeltaChange> updates = null, adds = null, deletes = null;
370:
371: //remove all xpaths from comp that are not contained in org => updates
372: List<String> update = new ArrayList<String>(comp);
373: update.retainAll(org);
374: for (String xp : update) {
375: if (xp.endsWith("/")) {
376: //group
377: xp = xp.substring(0, xp.length() - 1);
378: if (!compare.getGroupData(xp).equals(
379: original.getGroupData(xp))) {
380: if (updates == null)
381: updates = new ArrayList<FxDeltaChange>(10);
382: updates
383: .add(new FxDeltaChange(xp, original
384: .getGroupData(xp), compare
385: .getGroupData(xp)));
386: }
387: } else /*property*/if (!compare.getData(xp).equals(
388: original.getData(xp))) {
389: if (updates == null)
390: updates = new ArrayList<FxDeltaChange>(10);
391: updates.add(new FxDeltaChange(xp, original
392: .getPropertyData(xp), compare
393: .getPropertyData(xp)));
394: }
395: }
396:
397: List<String> add = new ArrayList<String>(comp);
398: add.removeAll(org);
399: for (String xp : add) {
400: if (adds == null)
401: adds = new ArrayList<FxDeltaChange>(10);
402: if (xp.endsWith("/")) {
403: xp = xp.substring(0, xp.length() - 1);
404: adds.add(new FxDeltaChange(xp, null, compare
405: .getGroupData(xp)));
406: } else
407: adds.add(new FxDeltaChange(xp, null, compare
408: .getPropertyData(xp)));
409: }
410:
411: List<String> rem = new ArrayList<String>(org);
412: rem.removeAll(comp);
413: for (String xp : rem) {
414: if (deletes == null)
415: deletes = new ArrayList<FxDeltaChange>(10);
416: if (xp.endsWith("/")) {
417: xp = xp.substring(0, xp.length() - 1);
418: deletes.add(new FxDeltaChange(xp, original
419: .getGroupData(xp), null));
420: } else
421: deletes.add(new FxDeltaChange(xp, original
422: .getPropertyData(xp), null));
423: }
424: return new FxDelta(updates, adds, deletes);
425: }
426: }
|