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}