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 */ 019 020package org.nuxeo.ecm.core.storage.sql; 021 022import java.io.Serializable; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.LinkedList; 027import java.util.List; 028 029import org.nuxeo.ecm.core.storage.sql.RowMapper.RowUpdate; 030 031/** 032 * A type of fragment corresponding to a single row in a table and its associated in-memory information (state, dirty 033 * fields, attached context). 034 */ 035public final class SimpleFragment extends Fragment { 036 037 private static final long serialVersionUID = 1L; 038 039 private static final Row UNKNOWN_ROW = new Row(null, (Serializable) null); 040 041 public static final SimpleFragment UNKNOWN = new SimpleFragment(UNKNOWN_ROW, State.DETACHED, null); 042 043 /** 044 * Constructs a {@link SimpleFragment} from a {@link Row}. 045 * 046 * @param row the row, or {@code null} 047 * @param state the initial state for the fragment 048 * @param context the persistence context to which the fragment is tied, or {@code null} 049 */ 050 public SimpleFragment(Row row, State state, PersistenceContext context) { 051 super(row, state, context); 052 } 053 054 @Override 055 protected State refetch() { 056 Row newrow = context.mapper.readSimpleRow(row); 057 if (newrow == null) { 058 row.size = 0; 059 return State.ABSENT; 060 } else { 061 row = newrow; 062 clearDirty(); 063 return State.PRISTINE; 064 } 065 } 066 067 @Override 068 protected State refetchDeleted() { 069 row.size = 0; 070 return State.ABSENT; 071 } 072 073 /** 074 * Gets a value by key. 075 * 076 * @param key the key 077 * @return the value 078 */ 079 public Serializable get(String key) { 080 accessed(); 081 return row.get(key); 082 } 083 084 /** 085 * Puts a value by key. 086 * 087 * @param key the key 088 * @param value the value 089 */ 090 public void put(String key, Serializable value) { 091 accessed(); // maybe refetch other values 092 row.put(key, value, oldvalues); // pass oldvalues to be able to deal with deltas 093 // resize oldvalues to follow row if needed 094 if (oldvalues.length < row.values.length) { 095 Serializable[] tmp = oldvalues; 096 oldvalues = new Serializable[row.values.length]; 097 System.arraycopy(tmp, 0, oldvalues, 0, tmp.length); 098 } 099 if (getState() != State.ABSENT || value != null) { 100 // don't mark modified when setting null in an absent fragment 101 // to avoid creating unneeded rows 102 markModified(); 103 } 104 } 105 106 /** 107 * Returns a {@code String} value. 108 * 109 * @param key the key 110 * @return the value as a {@code String} 111 * @throws ClassCastException if the value is not a {@code String} 112 */ 113 public String getString(String key) { 114 return (String) get(key); 115 } 116 117 @Override 118 public RowUpdate getRowUpdate() { 119 Collection<String> keys = getDirtyKeys(); 120 if (keys.isEmpty()) { 121 return null; 122 } 123 return new RowUpdate(row, keys); 124 } 125 126 /** 127 * Gets the dirty keys (keys of values changed since last clear). 128 * 129 * @return the dirty keys 130 */ 131 public List<String> getDirtyKeys() { 132 List<String> keys = null; 133 for (int i = 0; i < row.size; i++) { 134 if (!same(oldvalues[i], row.values[i])) { 135 if (keys == null) { 136 keys = new LinkedList<String>(); 137 } 138 keys.add(row.keys[i]); 139 } 140 } 141 return keys == null ? Collections.<String> emptyList() : keys; 142 } 143 144 private static boolean same(Object a, Object b) { 145 if (a == null) { 146 return b == null; 147 } else { 148 return a.equals(b); 149 } 150 } 151 152 /** 153 * Comparator of {@link SimpleFragment}s according to a field. 154 */ 155 public static class FieldComparator implements Comparator<SimpleFragment> { 156 157 public final String key; 158 159 public FieldComparator(String key) { 160 this.key = key; 161 } 162 163 @Override 164 public int compare(SimpleFragment frag1, SimpleFragment frag2) { 165 return doCompare(frag1, frag2); 166 } 167 168 // separate function because we need a free generic type 169 // which is incompatible with the super signature 170 @SuppressWarnings("unchecked") 171 public <T> int doCompare(SimpleFragment frag1, SimpleFragment frag2) { 172 Comparable<T> value1 = (Comparable<T>) frag1.get(key); 173 T value2 = (T) frag2.get(key); 174 if (value1 == null && value2 == null) { 175 // coherent sort 176 return frag1.hashCode() - frag2.hashCode(); 177 } 178 if (value1 == null) { 179 return 1; 180 } 181 if (value2 == null) { 182 return -1; 183 } 184 return value1.compareTo(value2); 185 } 186 } 187 188}