package org.wikibrain.core.dao.sql; import com.google.common.collect.Sets; import com.typesafe.config.Config; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; import org.apache.commons.lang3.tuple.Pair; import org.jooq.*; import org.wikibrain.conf.ConfigurationException; import org.wikibrain.conf.Configurator; import org.wikibrain.core.dao.DaoException; import org.wikibrain.core.dao.DaoFilter; import org.wikibrain.core.dao.UniversalLinkDao; import org.wikibrain.core.jooq.Tables; import org.wikibrain.core.lang.Language; import org.wikibrain.core.lang.LanguageSet; import org.wikibrain.core.model.UniversalLink; import org.wikibrain.core.model.UniversalLinkGroup; import org.wikibrain.utils.ObjectDb; import java.io.File; import java.io.IOException; import java.util.*; /** * An alternate implementation of a UniversalLinkSqlDao. * It ignores the underlying local link map of a universal link, and only * maintains the language set in the database via a byte array where each * element represents a single ID. IDs over 255 are represented by 2 bytes. * * @author Ari Weiland */ public class UniversalLinkSkeletalSqlDao extends AbstractSqlDao<UniversalLink> implements UniversalLinkDao { private final int algorithmId; private File path; private ObjectDb<byte[]> objectDb; public UniversalLinkSkeletalSqlDao(WpDataSource dataSource, int algorithmId) throws DaoException { super(dataSource, INSERT_FIELDS, "/db/universal-skeletal-link"); this.algorithmId = algorithmId; } private static final TableField [] INSERT_FIELDS = new TableField[] { Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID, Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID, Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID, Tables.UNIVERSAL_SKELETAL_LINK.LANGS }; @Override public void beginLoad() throws DaoException { super.beginLoad(); try { path = File.createTempFile("univ-links", "odb"); if (path.isFile()) { path.delete(); } path.mkdirs(); objectDb = new ObjectDb<byte[]>(path, true); } catch (IOException e) { throw new DaoException(e); } } @Override public void save(UniversalLink item) throws DaoException { try { int sourceId = item.getSourceId(); int destId = item.getDestId(); LanguageSet languages = item.getLanguageSet(); String key = sourceId + "_" + destId + "_" + item.getAlgorithmId(); byte[] temp = objectDb.get(key); if (temp != null) { languages = new LanguageSet(Sets.union(LanguageSet.getLanguageSet(temp).getLanguages(), languages.getLanguages())); } objectDb.put(key, languages.toByteArray()); } catch (IOException e) { throw new DaoException(e); } catch (ClassNotFoundException e) { throw new DaoException(e); } } @Override public void endLoad() throws DaoException { for (Pair<String, byte[]> pair : objectDb) { String[] ids = pair.getKey().split("_"); insert( Integer.valueOf(ids[0]), Integer.valueOf(ids[1]), Integer.valueOf(ids[2]), pair.getValue() ); } objectDb.close(); path.delete(); super.endLoad(); } @Override public Iterable<UniversalLink> get(DaoFilter daoFilter) throws DaoException { DSLContext context = getJooq(); try { Collection<Condition> conditions = new ArrayList<Condition>(); if (daoFilter.getNameSpaceIds() != null) { conditions.add(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID.in(daoFilter.getSourceIds())); } if (daoFilter.getNameSpaceIds() != null) { conditions.add(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID.in(daoFilter.getDestIds())); } if (daoFilter.isRedirect() != null) { conditions.add(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)); } Cursor<Record> result = context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(conditions) .limit(daoFilter.getLimitOrInfinity()) .fetchLazy(getFetchSize()); return new SimpleSqlDaoIterable<UniversalLink>(result, context) { @Override public UniversalLink transform(Record item) throws DaoException { return buildUniversalLink(item); } }; } catch (RuntimeException e) { freeJooq(context); throw e; } } @Override public int getCount(DaoFilter daoFilter) throws DaoException { DSLContext context = getJooq(); try { Collection<Condition> conditions = new ArrayList<Condition>(); if (daoFilter.getNameSpaceIds() != null) { conditions.add(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID.in(daoFilter.getSourceIds())); } if (daoFilter.getNameSpaceIds() != null) { conditions.add(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID.in(daoFilter.getDestIds())); } if (daoFilter.isRedirect() != null) { conditions.add(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)); } return context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(conditions) .fetchCount(); } finally { freeJooq(context); } } @Override public UniversalLinkGroup getOutlinks(int sourceId) throws DaoException { DSLContext context = getJooq(); try { Result<Record> result = context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID.eq(sourceId)) .and(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)) .fetch(); return buildUniversalLinkGroup(result, true); } finally { freeJooq(context); } } @Override public UniversalLinkGroup getInlinks(int destId) throws DaoException { DSLContext context = getJooq(); try { Result<Record> result = context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID.eq(destId)) .and(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)) .fetch(); return buildUniversalLinkGroup(result, false); } finally { freeJooq(context); } } @Override public TIntSet getOutlinkIds(int sourceId) throws DaoException { DSLContext context = getJooq(); try { Cursor<Record> result = context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID.eq(sourceId)) .and(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)) .fetchLazy(getFetchSize()); TIntSet ids = new TIntHashSet(); for (Record record : result){ ids.add(record.getValue(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID)); } return ids; } finally { freeJooq(context); } } @Override public TIntSet getInlinkIds(int destId) throws DaoException { DSLContext context = getJooq(); try { Cursor<Record> result = context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID.eq(destId)) .and(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)) .fetchLazy(getFetchSize()); TIntSet ids = new TIntHashSet(); for (Record record : result){ ids.add(record.getValue(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID)); } return ids; } finally { freeJooq(context); } } @Override public UniversalLink getUniversalLink(int sourceId, int destId) throws DaoException { DSLContext context = getJooq(); try { Record record = context.select() .from(Tables.UNIVERSAL_SKELETAL_LINK) .where(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID.eq(sourceId)) .and(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID.eq(destId)) .and(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID.eq(algorithmId)) .fetchOne(); return buildUniversalLink(record); } finally { freeJooq(context); } } private UniversalLinkGroup buildUniversalLinkGroup(Result<Record> result, boolean outlinks) throws DaoException { if (result == null || result.isEmpty()) { return null; } Map<Integer, UniversalLink> map = new HashMap<Integer, UniversalLink>(); int commonId = -1; int algorithmId = -1; for (Record record : result) { map.put( record.getValue(outlinks ? // Gets the unique ID of the links Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID : // If links are outlinks, dest ID is unique Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID), // If links are inlinks, source ID is unique buildUniversalLink(record)); if (commonId == -1) { commonId = record.getValue(outlinks ? // Gets the common ID of the links Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID : // If links are outlinks, source ID is common Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID); // If links are inlinks, dest ID is common; algorithmId = record.getValue(Tables.UNIVERSAL_LINK.ALGORITHM_ID); } } Set<Language> languages = new HashSet<Language>(); for (UniversalLink link : map.values()) { for (Language language : link.getLanguageSet()) { languages.add(language); } } return new UniversalLinkGroup( map, outlinks, commonId, algorithmId, new LanguageSet(languages) ); } private UniversalLink buildUniversalLink(Record record) throws DaoException { if (record == null) { return null; } return new UniversalLink( record.getValue(Tables.UNIVERSAL_SKELETAL_LINK.SOURCE_ID), record.getValue(Tables.UNIVERSAL_SKELETAL_LINK.DEST_ID), record.getValue(Tables.UNIVERSAL_SKELETAL_LINK.ALGORITHM_ID), LanguageSet.getLanguageSet(record.getValue(Tables.UNIVERSAL_SKELETAL_LINK.LANGS)) ); } public static class Provider extends org.wikibrain.conf.Provider<UniversalLinkDao> { public Provider(Configurator configurator, org.wikibrain.conf.Configuration config) throws ConfigurationException { super(configurator, config); } @Override public Class getType() { return UniversalLinkDao.class; } @Override public String getPath() { return "dao.universalLink"; } @Override public UniversalLinkDao get(String name, Config config, Map<String, String> runtimeParams) throws ConfigurationException { if (!config.getString("type").equals("skeletal-sql")) { return null; } try { int algorithmId = getConfig().get().getInt("mapper." + config.getString("mapper") + ".algorithmId"); return new UniversalLinkSkeletalSqlDao( getConfigurator().get( WpDataSource.class, config.getString("dataSource")), algorithmId ); } catch (DaoException e) { throw new ConfigurationException(e); } } } }