001package dk.netarkivet.harvester.datamodel.eav; 002 003import java.sql.Connection; 004import java.sql.ResultSet; 005import java.sql.SQLException; 006import java.util.ArrayList; 007import java.util.Collections; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Map; 011import java.util.Set; 012import java.util.TreeMap; 013 014import org.apache.commons.lang.StringUtils; 015import org.slf4j.Logger; 016import org.slf4j.LoggerFactory; 017 018import com.antiaction.raptor.dao.AttributeBase; 019import com.antiaction.raptor.dao.AttributeTypeBase; 020import com.antiaction.raptor.sql.DBWrapper; 021import com.antiaction.raptor.sql.SqlResult; 022import com.antiaction.raptor.sql.mssql.MSSql; 023 024import dk.netarkivet.harvester.datamodel.HarvestDBConnection; 025 026/** 027 * EAV wrapper for the actual EAV implementation. 028 */ 029public class EAV { 030 031 private static final Logger log = LoggerFactory.getLogger(EAV.class); 032 033 /** tree id for <code>SparseFullHarvest</code> attributes and types. */ 034 public static final int SNAPSHOT_TREE_ID = 1; 035 /** tree id for <code>DomainConfiguration</code> attributes and types. */ 036 public static final int DOMAIN_TREE_ID = 2; 037 038 /** Classloader used to instantiate attribute type implementations. */ 039 protected static ClassLoader classLoader = EAV.class.getClassLoader(); 040 041 /** Singleton instance. */ 042 protected static EAV instance; 043 044 /** 045 * @return an instance of this class. 046 */ 047 public static synchronized EAV getInstance() { 048 if (instance == null) { 049 instance = new EAV(); 050 } 051 return instance; 052 } 053 054 /** DB layer implementation. MSSQL is more like SQL92. */ 055 protected DBWrapper db = MSSql.getInstance(null, classLoader); 056 057 /** 058 * Insert attribute into database. 059 * @param attribute attribute to insert into database 060 */ 061 public void insertAttribute(AttributeBase attribute) { 062 Connection connection = HarvestDBConnection.get(); 063 db.attribute_insert(connection, attribute); 064 HarvestDBConnection.release(connection); 065 } 066 067 /** 068 * Update existing attribute. 069 * @param attribute attribute to update in database 070 */ 071 public void saveAttribute(AttributeBase attribute) { 072 Connection connection = HarvestDBConnection.get(); 073 attribute.saveState(db, connection); 074 HarvestDBConnection.release(connection); 075 } 076 077 /** 078 * Returns a list of attribute types for the given tree id. 079 * @param tree_id tree id to look for attribute types in 080 * @return a list of attribute types for the given tree id 081 */ 082 public List<AttributeTypeBase> getAttributeTypes(int tree_id) { 083 Connection connection = HarvestDBConnection.get(); 084 List<AttributeTypeBase> attributeTypes = db.getAttributeTypes(connection, classLoader, tree_id); 085 HarvestDBConnection.release(connection); 086 return attributeTypes; 087 } 088 089 /** 090 * Handy class to pair an attribute and its type. 091 */ 092 public static class AttributeAndType implements Comparable<AttributeAndType> { 093 public AttributeTypeBase attributeType; 094 public AttributeBase attribute; 095 public AttributeAndType(AttributeTypeBase attributeType, AttributeBase attribute) { 096 this.attributeType = attributeType; 097 this.attribute = attribute; 098 } 099 100 /** 101 * Comparator allows a list to be sorted so attributes appear in a reproducible order. 102 * @param o 103 * @return 104 */ 105 @Override public int compareTo(AttributeAndType o) { 106 return attributeType.id - o.attributeType.id; 107 } 108 } 109 110 /** 111 * Returns a list of attributes and their type for a given entity id and tree id. 112 * @param tree_id tree id to look in 113 * @param entity_id entity to look for 114 * @return a list of attributes and their type for a given entity id and tree id. 115 * @throws SQLException if an SQL exception occurs while querying the database 116 */ 117 public List<AttributeAndType> getAttributesAndTypes(int tree_id, int entity_id) throws SQLException { 118 Connection connection = HarvestDBConnection.get(); 119 List<AttributeTypeBase> attributeTypes = db.getAttributeTypes(connection, classLoader, tree_id); 120 AttributeTypeBase attributeType; 121 Map<Integer, AttributeTypeBase> attributeTypesMap = new TreeMap<Integer, AttributeTypeBase>(); 122 for (int i=0; i<attributeTypes.size(); ++i) { 123 attributeType = attributeTypes.get(i); 124 attributeTypesMap.put(attributeType.id, attributeType); 125 } 126 SqlResult sqlResult = db.attributes_getTypedAttributes(connection, tree_id, entity_id); 127 List<AttributeAndType> attributes = new ArrayList<AttributeAndType>(); 128 AttributeBase attribute; 129 ResultSet rs = sqlResult.rs; 130 while ( rs.next() ) { 131 int type_id = rs.getInt("type_id"); 132 attributeType = attributeTypesMap.get(type_id); 133 attribute = null; 134 rs.getInt("id"); 135 if (attributeType != null && !rs.wasNull()) { 136 attribute = attributeType.instanceOf(); 137 attribute.attributeType = attributeType; 138 attribute.loadState( db, connection, rs ); 139 } 140 attributes.add(new AttributeAndType(attributeType, attribute)); 141 } 142 sqlResult.close(); 143 HarvestDBConnection.release(connection); 144 return attributes; 145 } 146 147 /** 148 * Compare two lists containing attributes and their types. 149 * @param antList1 150 * @param antList2 151 * @return the result of comparing two lists containing attributes and their types 152 */ 153 public static int compare(List<AttributeAndType> antList1, List<AttributeAndType> antList2) { 154 //For the vast majority of domains, we just have default attributes so let's deal 155 //with that case efficiently right away: 156 157 if (antList1 == null && antList2 == null) { 158 return 0; 159 } 160 if (antList1 == null) { 161 antList1 = new ArrayList<>(); 162 } 163 if (antList2 == null) { 164 antList2 = new ArrayList<>(); 165 } 166 if (antList1.isEmpty() && antList2.isEmpty()) { 167 return 0; 168 } 169 int size1 = antList1.size(); 170 int size2 = antList2.size(); 171 if (!antList1.isEmpty() && !antList2.isEmpty() && size1 != size2) { // both non-empty but different length 172 throw new UnsupportedOperationException("Haven't though about how to compare attribute lists of different lengths"); 173 } 174 175 //Case where one list is empty, the other isn't. Check that all values are equal to default. 176 if (antList1.isEmpty() || antList2.isEmpty()) { 177 boolean antList2Empty = antList2.isEmpty(); 178 List<AttributeAndType> antList3 = new ArrayList<AttributeAndType>(); 179 antList3.addAll(antList1); 180 antList3.addAll(antList2); 181 for (AttributeAndType attributeAndType: antList3) { 182 Integer defaultValue = attributeAndType.attributeType.def_int; 183 184 Integer value = null; 185 if (defaultValue == null) { 186 defaultValue = 0; 187 } 188 189 if (attributeAndType.attribute != null) { 190 value = attributeAndType.attribute.getInteger(); 191 } 192 193 if (value == null) { 194 value = defaultValue; 195 } 196 197 198 int res = value -defaultValue; 199 200 if (res != 0L) { // value different from default 201 int result = res < 0L ? -1 : 1; 202 if (antList2Empty) { 203 result = -result; 204 } 205 return result; 206 } 207 } 208 return 0; 209 } 210 211 //Case where neither list is empty. Compare attribute by attribute. 212 Collections.sort(antList1); 213 Collections.sort(antList2); 214 for (int i = 0; i < size1; i++) { 215 AttributeAndType ant1 = antList1.get(i); 216 AttributeAndType ant2 = antList2.get(i); 217 if (ant1.attributeType.id != ant2.attributeType.id) { 218 return (ant1.attributeType.id - ant2.attributeType.id) < 0 ? -1 : 1; 219 } 220 if (ant1.attributeType.datatype != 1) { 221 throw new UnsupportedOperationException("EAV attribute datatype compare not implemented yet."); 222 } 223 Integer i1 = null; 224 Integer i2 = null; 225 if (ant1.attribute != null) { 226 i1 = ant1.attribute.getInteger(); 227 } 228 if (i1 == null) { 229 i1 = ant1.attributeType.def_int; 230 } 231 if (i1 == null) { 232 i1 = 0; 233 } 234 if (ant2.attribute != null) { 235 i2 = ant2.attribute.getInteger(); 236 } 237 if (i2 == null) { 238 i2 = ant2.attributeType.def_int; 239 } 240 if (i2 == null) { 241 i2 = 0; 242 } 243 int res = i2 - i1; 244 if (res != 0L) { 245 return res < 0L ? -1 : 1; 246 } 247 } 248 return 0; 249 } 250 251 252 253 /////////////////// Utility methods ////////////////////////////////////// 254 /** 255 * Get list of attribute names for a specific tree_id 256 * @param tree_id a given tree_id 257 * @return list of attribute names for a specific tree_id 258 */ 259 public static Set<String> getAttributeNames(int tree_id) { 260 EAV eav = EAV.getInstance(); 261 Set<String> attributeNames = new HashSet<String>(); 262 List<AttributeTypeBase> attributeTypes = eav.getAttributeTypes(tree_id); 263 for (AttributeTypeBase atb: attributeTypes) { 264 log.trace("Adding {} to list of attributenames", atb.name); 265 attributeNames.add(atb.name); 266 } 267 log.debug("The list of available attributeNames are {}", StringUtils.join(attributeNames, ",")); 268 return attributeNames; 269 } 270 271}