/* * Copyright (C) 2012 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl.quota; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.storage.WorkspaceDataContainer; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Wraps information of changed data size of particular save. First, is put into pending changes * and after save is performed being moved into changes log. * * @author <a href="abazko@exoplatform.com">Anatoliy Bazko</a> * @version $Id: ChangesItem.java 34360 2009-07-22 23:58:59Z tolusha $ */ class ChangesItem implements Externalizable { /** * ChangesItem constructor. */ public ChangesItem() { } /** * Contains calculated workspace data size changes of particular save. */ private long workspaceChangedSize; /** * Contains calculated node data size changes of particular save. * Represents {@link Map} with absolute node path as key and changed node * data size as value respectively. */ private Map<String, Long> calculatedChangedNodesSize = new HashMap<String, Long>(); /** * Set of absolute nodes paths for which changes were made but changed size is unknown. Most * famous case when {@link WorkspaceDataContainer#TRIGGER_EVENTS_FOR_DESCENDANTS_ON_RENAME} is * set to false and move operation is performed. */ private Set<String> unknownChangedNodesSize = new HashSet<String>(); /** * Collects node paths for which data size updating must be performed asynchronously. */ private Set<String> asyncUpdate = new HashSet<String>(); /** * Getter for {@link #workspaceChangedSize}. */ public long getWorkspaceChangedSize() { return workspaceChangedSize; } /** * Updates {@link #workspaceChangedSize}. */ public void updateWorkspaceChangedSize(long delta) { workspaceChangedSize += delta; } /** * Returns node data changed size if exists or zero otherwise. */ public long getNodeChangedSize(String nodePath) { Long delta = calculatedChangedNodesSize.get(nodePath); return delta == null ? 0 : delta; } /** * Getter for {@link #calculatedChangedNodesSize}. */ public Map<String, Long> getAllNodesCalculatedChangedSize() { return calculatedChangedNodesSize; } /** * Getter for {@link #unknownChangedNodesSize}. */ public Set<String> getAllNodesUnknownChangedSize() { return unknownChangedNodesSize; } /** * Updates {@link #calculatedChangedNodesSize} for particular * node path. */ public void updateNodeChangedSize(String nodePath, long delta) { Long oldDelta = calculatedChangedNodesSize.get(nodePath); Long newDelta = delta + (oldDelta != null ? oldDelta : 0); calculatedChangedNodesSize.put(nodePath, newDelta); } /** * Adds new node absolute path for {@link #unknownChangedNodesSize} collection. */ public void addPathWithUnknownChangedSize(String nodePath) { unknownChangedNodesSize.add(nodePath); } /** * Adds new node absolute path for {@link #asyncUpdate} collection. */ public void addPathWithAsyncUpdate(String nodePath) { asyncUpdate.add(nodePath); } /** * Merges current changes with new one. */ public void merge(ChangesItem changesItem) { workspaceChangedSize += changesItem.getWorkspaceChangedSize(); for (Entry<String, Long> changesEntry : changesItem.calculatedChangedNodesSize.entrySet()) { String nodePath = changesEntry.getKey(); Long currentDelta = changesEntry.getValue(); Long oldDelta = calculatedChangedNodesSize.get(nodePath); Long newDelta = currentDelta + (oldDelta == null ? 0 : oldDelta); calculatedChangedNodesSize.put(nodePath, newDelta); } for (String path : changesItem.unknownChangedNodesSize) { unknownChangedNodesSize.add(path); } for (String path : changesItem.asyncUpdate) { asyncUpdate.add(path); } } /** * Checks if there is any changes. */ public boolean isEmpty() { return workspaceChangedSize == 0 && calculatedChangedNodesSize.isEmpty() && unknownChangedNodesSize.isEmpty(); } /** * Leave in {@link ChangesItem} only changes being apply asynchronously * and return ones to apply instantly. */ public ChangesItem extractSyncChanges() { ChangesItem syncChangesItem = new ChangesItem(); Iterator<String> iter = calculatedChangedNodesSize.keySet().iterator(); while (iter.hasNext() && !asyncUpdate.isEmpty()) { String nodePath = iter.next(); if (!asyncUpdate.contains(nodePath)) { Long chanagedSize = calculatedChangedNodesSize.get(nodePath); syncChangesItem.calculatedChangedNodesSize.put(nodePath, chanagedSize); syncChangesItem.workspaceChangedSize += chanagedSize; this.asyncUpdate.remove(nodePath); this.workspaceChangedSize -= chanagedSize; iter.remove(); } } return syncChangesItem; } /** * {@inheritDoc} */ public void writeExternal(ObjectOutput out) throws IOException { out.writeLong(workspaceChangedSize); out.writeInt(calculatedChangedNodesSize.size()); for (Entry<String, Long> entry : calculatedChangedNodesSize.entrySet()) { writeString(out, entry.getKey()); out.writeLong(entry.getValue()); } out.writeInt(unknownChangedNodesSize.size()); for (String path : unknownChangedNodesSize) { writeString(out, path); } out.writeInt(asyncUpdate.size()); for (String path : asyncUpdate) { writeString(out, path); } } private void writeString(ObjectOutput out, String str) throws IOException { byte[] data = str.getBytes(Constants.DEFAULT_ENCODING); out.writeInt(data.length); out.write(data); } /** * {@inheritDoc} */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.workspaceChangedSize = in.readLong(); int size = in.readInt(); this.calculatedChangedNodesSize = new HashMap<String, Long>(size); for (int i = 0; i < size; i++) { String nodePath = readString(in); Long delta = in.readLong(); calculatedChangedNodesSize.put(nodePath, delta); } size = in.readInt(); this.unknownChangedNodesSize = new HashSet<String>(size); for (int i = 0; i < size; i++) { String nodePath = readString(in); unknownChangedNodesSize.add(nodePath); } size = in.readInt(); this.asyncUpdate = new HashSet<String>(size); for (int i = 0; i < size; i++) { String nodePath = readString(in); asyncUpdate.add(nodePath); } } private String readString(ObjectInput in) throws IOException { byte[] data = new byte[in.readInt()]; in.readFully(data); return new String(data, Constants.DEFAULT_ENCODING); } }