001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (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.Serializable; 022import java.util.ArrayList; 023import java.util.Calendar; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Objects; 028 029import org.nuxeo.ecm.core.api.model.Delta; 030 031/** 032 * The data of a single row in a table (keys/values form a map), or of multiple rows with the same id (values is an 033 * array of Serializable). 034 * <p> 035 * The id of the row is distinguished internally from other columns. For fragments corresponding to created data, the 036 * initial id is a temporary one, and it will be changed after database insert. 037 */ 038public final class Row extends RowId implements Serializable, Cloneable { 039 040 private static final long serialVersionUID = 1L; 041 042 private static final int DEFAULT = 5; 043 044 private enum OpaqueValue { 045 OPAQUE_VALUE 046 } 047 048 /** 049 * A database value we don't care about reading. When present in a fragment, it won't be written, but any other 050 * value will be. 051 */ 052 public static final Serializable OPAQUE = OpaqueValue.OPAQUE_VALUE; 053 054 /** 055 * The row keys, for single row. 056 */ 057 protected String[] keys; 058 059 /** 060 * The row values. 061 */ 062 public Serializable[] values; 063 064 /** 065 * The size of the allocated part of {@link #values}, for single rows. 066 */ 067 protected int size; 068 069 /** Copy constructor. */ 070 private Row(Row row) { 071 super(row); 072 keys = row.keys == null ? null : row.keys.clone(); 073 values = row.values == null ? null : row.values.clone(); 074 size = row.size; 075 } 076 077 @Override 078 public Row clone() { 079 return new Row(this); 080 } 081 082 /** 083 * Constructs an empty {@link Row} for the given table with the given id (may be {@code null}). 084 */ 085 public Row(String tableName, Serializable id) { 086 super(tableName, id); 087 keys = new String[DEFAULT]; 088 values = new Serializable[DEFAULT]; 089 // size = 0; 090 } 091 092 /** 093 * Constructs a new {@link Row} from a map. 094 * 095 * @param map the initial data to use 096 */ 097 public Row(String tableName, Map<String, Serializable> map) { 098 super(tableName, null); // id set through map 099 keys = new String[map.size()]; 100 values = new Serializable[map.size()]; 101 // size = 0; 102 for (Entry<String, Serializable> entry : map.entrySet()) { 103 putNew(entry.getKey(), entry.getValue()); 104 } 105 } 106 107 /** 108 * Constructs a new {@link Row} from an array of values. 109 * 110 * @param array the initial data to use 111 */ 112 public Row(String tableName, Serializable id, Serializable[] array) { 113 super(tableName, id); 114 values = array.clone(); 115 keys = null; 116 size = -1; 117 } 118 119 public boolean isCollection() { 120 return size == -1; 121 } 122 123 private void ensureCapacity(int minCapacity) { 124 if (minCapacity > values.length) { 125 Serializable[] k = keys; 126 Serializable[] d = values; 127 int newCapacity = (values.length * 3) / 2 + 1; 128 if (newCapacity < minCapacity) { 129 newCapacity = minCapacity; 130 } 131 keys = new String[newCapacity]; 132 values = new Serializable[newCapacity]; 133 System.arraycopy(d, 0, values, 0, size); 134 System.arraycopy(k, 0, keys, 0, size); 135 } 136 } 137 138 /** 139 * Puts a key/value. Does not deal with deltas. 140 * 141 * @param key the key 142 * @param value the value 143 */ 144 public void put(String key, Serializable value) { 145 if (key.equals(Model.MAIN_KEY)) { 146 id = value; 147 return; 148 } 149 // linear search but the array is small 150 for (int i = 0; i < size; i++) { 151 if (key.equals(keys[i])) { 152 values[i] = value; 153 return; 154 } 155 } 156 ensureCapacity(size + 1); 157 keys[size] = key == null ? null: key.intern(); 158 values[size++] = value; 159 } 160 161 /** 162 * Puts a key/value where the current or new value may be a delta. To resolve a delta, the oldvalues (in-database 163 * state) must be consulted. 164 * 165 * @param key the key 166 * @param value the value 167 * @param oldvalues the old values 168 */ 169 public void put(String key, Serializable value, Serializable[] oldvalues) { 170 if (key.equals(Model.MAIN_KEY)) { 171 id = value; 172 return; 173 } 174 // linear search but the array is small 175 for (int i = 0; i < size; i++) { 176 if (key.equals(keys[i])) { 177 if (value instanceof Delta) { 178 // the new value is a delta 179 Delta delta = (Delta) value; 180 Serializable deltaBase = delta.getBase(); 181 Serializable oldValue = oldvalues[i]; 182 if (!Objects.equals(oldValue, deltaBase)) { 183 // delta's base is not the in-database value 184 // -> set a new value, don't use a delta update 185 value = delta.getFullValue(); 186 } 187 // else delta's base is the in-database value 188 // because base is consistent with old value, assume the delta is already properly computed 189 } 190 // else use the new non-delta value 191 values[i] = value; 192 return; 193 } 194 } 195 ensureCapacity(size + 1); 196 keys[size] = key == null ? null: key.intern(); 197 values[size++] = value; 198 } 199 200 /** 201 * Puts a key/value, assuming the key is not already there. 202 * 203 * @param key the key 204 * @param value the value 205 */ 206 public void putNew(String key, Serializable value) { 207 if (key.equals(Model.MAIN_KEY)) { 208 id = value; 209 return; 210 } 211 ensureCapacity(size + 1); 212 keys[size] = key == null ? null : key.intern(); 213 values[size++] = value; 214 } 215 216 /** 217 * Gets a value from a key. 218 * 219 * @param key the key 220 * @return the value 221 */ 222 public Serializable get(String key) { 223 if (key.equals(Model.MAIN_KEY)) { 224 return id; 225 } 226 // linear search but the array is small 227 for (int i = 0; i < size; i++) { 228 if (key.equals(keys[i])) { 229 return values[i]; 230 } 231 } 232 return null; 233 } 234 235 /** 236 * Gets the list of keys. The id is not included. 237 */ 238 public List<String> getKeys() { 239 List<String> list = new ArrayList<String>(size); 240 for (int i = 0; i < size; i++) { 241 list.add(keys[i]); 242 } 243 return list; 244 } 245 246 /** 247 * Gets the list of values. The id is not included. 248 */ 249 public List<Serializable> getValues() { 250 List<Serializable> list = new ArrayList<Serializable>(size); 251 for (int i = 0; i < size; i++) { 252 list.add(values[i]); 253 } 254 return list; 255 } 256 257 @Override 258 public String toString() { 259 StringBuilder buf = new StringBuilder(); 260 buf.append(getClass().getSimpleName()); 261 buf.append('('); 262 buf.append(tableName); 263 buf.append(", "); 264 buf.append(id); 265 if (size != -1) { 266 // single row 267 buf.append(", {"); 268 for (int i = 0; i < size; i++) { 269 if (i > 0) { 270 buf.append(", "); 271 } 272 buf.append(keys[i]); 273 buf.append('='); 274 printValue(values[i], buf); 275 } 276 buf.append('}'); 277 } else { 278 // multiple rows 279 buf.append(", ["); 280 for (int i = 0; i < values.length; i++) { 281 if (i > 0) { 282 buf.append(", "); 283 } 284 printValue(values[i], buf); 285 } 286 buf.append(']'); 287 } 288 buf.append(')'); 289 return buf.toString(); 290 } 291 292 public static final int MAX_STRING = 100; 293 294 public static final int MAX_ARRAY = 10; 295 296 @SuppressWarnings("boxing") 297 public static void printValue(Serializable value, StringBuilder buf) { 298 if (value == null) { 299 buf.append("NULL"); 300 } else if (value instanceof String) { 301 String v = (String) value; 302 if (v.length() > MAX_STRING) { 303 v = v.substring(0, MAX_STRING) + "...(" + v.length() + " chars)..."; 304 } 305 buf.append('"'); 306 buf.append(v); 307 buf.append('"'); 308 } else if (value instanceof Calendar) { 309 Calendar cal = (Calendar) value; 310 char sign; 311 int offset = cal.getTimeZone().getOffset(cal.getTimeInMillis()) / 60000; 312 if (offset < 0) { 313 offset = -offset; 314 sign = '-'; 315 } else { 316 sign = '+'; 317 } 318 buf.append(String.format("Calendar(%04d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d)", cal.get(Calendar.YEAR), // 319 cal.get(Calendar.MONTH) + 1, // 320 cal.get(Calendar.DAY_OF_MONTH), // 321 cal.get(Calendar.HOUR_OF_DAY), // 322 cal.get(Calendar.MINUTE), // 323 cal.get(Calendar.SECOND), // 324 cal.get(Calendar.MILLISECOND), // 325 sign, offset / 60, offset % 60)); 326 } else if (value.getClass().isArray()) { 327 Serializable[] v = (Serializable[]) value; 328 buf.append('['); 329 for (int i = 0; i < v.length; i++) { 330 if (i > 0) { 331 buf.append(','); 332 if (i > MAX_ARRAY) { 333 buf.append("...("); 334 buf.append(v.length); 335 buf.append(" items)..."); 336 break; 337 } 338 } 339 printValue(v[i], buf); 340 } 341 buf.append(']'); 342 } else { 343 buf.append(value.toString()); 344 } 345 } 346 347}