001/*
002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.storage.sql;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.ObjectInputStream;
024import java.io.ObjectOutputStream;
025import java.io.OutputStream;
026import java.io.Serializable;
027import java.util.HashSet;
028import java.util.Set;
029
030import org.nuxeo.ecm.core.pubsub.SerializableInvalidations;
031
032/**
033 * A set of invalidations.
034 * <p>
035 * Records both modified and deleted fragments, as well as "parents modified" fragments.
036 */
037public class Invalidations implements SerializableInvalidations {
038
039    private static final long serialVersionUID = 1L;
040
041    /** Pseudo-table for children invalidation. */
042    public static final String PARENT = "__PARENT__";
043
044    /** Pseudo-table for series proxies invalidation. */
045    public static final String SERIES_PROXIES = "__SERIES_PROXIES__";
046
047    /** Pseudo-table for target proxies invalidation. */
048    public static final String TARGET_PROXIES = "__TARGET_PROXIES__";
049
050    public static final int MODIFIED = 1;
051
052    public static final int DELETED = 2;
053
054    /**
055     * Maximum number of invalidations kept, after which only {@link #all} is set. This avoids accumulating too many
056     * invalidations in memory, at the expense of more coarse-grained invalidations.
057     */
058    public static final int MAX_SIZE = 10000;
059
060    /**
061     * Used locally when invalidating everything, or when too many invalidations have been received.
062     */
063    public boolean all;
064
065    /** null when empty */
066    public Set<RowId> modified;
067
068    /** null when empty */
069    public Set<RowId> deleted;
070
071    public Invalidations() {
072    }
073
074    public Invalidations(boolean all) {
075        this.all = all;
076    }
077
078    @Override
079    public boolean isEmpty() {
080        return modified == null && deleted == null && !all;
081    }
082
083    public void clear() {
084        all = false;
085        modified = null;
086        deleted = null;
087    }
088
089    protected void setAll() {
090        all = true;
091        modified = null;
092        deleted = null;
093    }
094
095    protected void checkMaxSize() {
096        if (modified != null && modified.size() > MAX_SIZE //
097                || deleted != null && deleted.size() > MAX_SIZE) {
098            setAll();
099        }
100    }
101
102    /** only call this if it's to add at least one element in the set */
103    public Set<RowId> getKindSet(int kind) {
104        switch (kind) {
105        case MODIFIED:
106            if (modified == null) {
107                modified = new HashSet<RowId>();
108            }
109            return modified;
110        case DELETED:
111            if (deleted == null) {
112                deleted = new HashSet<RowId>();
113            }
114            return deleted;
115        }
116        throw new AssertionError();
117    }
118
119    @Override
120    public void add(SerializableInvalidations o) {
121        Invalidations other = (Invalidations) o;
122        if (other == null) {
123            return;
124        }
125        if (all) {
126            return;
127        }
128        if (other.all) {
129            setAll();
130            return;
131        }
132        if (other.modified != null) {
133            if (modified == null) {
134                modified = new HashSet<RowId>();
135            }
136            modified.addAll(other.modified);
137        }
138        if (other.deleted != null) {
139            if (deleted == null) {
140                deleted = new HashSet<RowId>();
141            }
142            deleted.addAll(other.deleted);
143        }
144        checkMaxSize();
145    }
146
147    public void addModified(RowId rowId) {
148        if (all) {
149            return;
150        }
151        if (modified == null) {
152            modified = new HashSet<RowId>();
153        }
154        modified.add(rowId);
155        checkMaxSize();
156    }
157
158    public void addDeleted(RowId rowId) {
159        if (all) {
160            return;
161        }
162        if (deleted == null) {
163            deleted = new HashSet<RowId>();
164        }
165        deleted.add(rowId);
166        checkMaxSize();
167    }
168
169    public void add(Serializable id, String[] tableNames, int kind) {
170        if (tableNames.length == 0) {
171            return;
172        }
173        Set<RowId> set = getKindSet(kind);
174        for (String tableName : tableNames) {
175            set.add(new RowId(tableName, id));
176        }
177        checkMaxSize();
178    }
179
180    // TODO do a more fine-grained serialization than using ObjectOutputStream
181
182    @Override
183    public void serialize(OutputStream out) throws IOException {
184        try (ObjectOutputStream oout = new ObjectOutputStream(out)) {
185            oout.writeObject(this);
186        }
187    }
188
189    public static Invalidations deserialize(InputStream in) throws IOException {
190        try (ObjectInputStream oin = new ObjectInputStream(in)) {
191            return (Invalidations) oin.readObject();
192        } catch (ClassNotFoundException | ClassCastException e) {
193            throw new IOException(e);
194        }
195    }
196
197    @Override
198    public String toString() {
199        StringBuilder sb = new StringBuilder(this.getClass().getSimpleName() + '(');
200        if (all) {
201            sb.append("all=true");
202        }
203        if (modified != null) {
204            sb.append("modified=");
205            sb.append(modified);
206            if (deleted != null) {
207                sb.append(',');
208            }
209        }
210        if (deleted != null) {
211            sb.append("deleted=");
212            sb.append(deleted);
213        }
214        sb.append(')');
215        return sb.toString();
216    }
217
218}