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}