Java tutorial
/* * SonarQube, open source software quality management tool. * Copyright (C) 2008-2014 SonarSource * mailto:contact AT sonarsource DOT com * * SonarQube is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * SonarQube is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.server.db; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.apache.ibatis.session.ResultContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.utils.System2; import org.sonar.core.persistence.BatchSession; import org.sonar.core.persistence.DaoComponent; import org.sonar.core.persistence.DbSession; import org.sonar.core.persistence.Dto; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.search.DbSynchronizationHandler; import org.sonar.server.search.IndexDefinition; import org.sonar.server.search.action.DeleteKey; import org.sonar.server.search.action.DeleteNestedItem; import org.sonar.server.search.action.InsertDto; import org.sonar.server.search.action.RefreshIndex; import org.sonar.server.search.action.UpsertDto; import org.sonar.server.search.action.UpsertNestedItem; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.io.Serializable; import java.sql.Timestamp; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; /** * naming convention for DAO * ========================= * <p/> * The DAO manages a Business Domain for a set of DTO. There is a Main DTO (i.e. RuleDto) * that has a few nested/child DTOs. The DAO supports nested DTOs for 1-to-1 and 1-to-many * relations. Many-to-many relations are handled by their own DAO classes (i.e. ActiveRuleDao) * <p/> * Main DTO * ------------------------- * <p/> * * GET Methods * - returns a single DTO * - DTO is fully loaded (no field will return null) * - returns null (and not empty) * - examples: * - RuleDto = ruleDao.getNullableByKey(dto.getKey()); * <p/> * * FIND Methods * - returns a List of DTO. * - Returns an empty list id no match * - method name is FULLY-NOMINATIVE * - examples: * - List<RuleDto> rules findByQualityProfile(QualityProfile qprofile) * - List<RuleDto> rules findByQualityProfile(QualityProfileKey qprofileKey) * - List<RuleDto> rules findByQualityProfileAndCreatedAfter(QualityProfileKey qprofileKey, Date date) * <p/> * * CRUD Methods * - insert(DTO) * - udpate(DTO) * - delete(DTO) * <p/> * Nested DTO * ------------------------- * <p/> * Some DAO implementations also manage nested DTO. RuleTag for example is managed by the RuleDao class * Nested DTO are accessible following a similar convention for the Main DTO: * <p/> * * GET Methods * - returns a single DTO * - DTO is fully loaded (no field will return null) * - returns null (and not empty) * - prefixed with DTO type * - examples: * - RuleTagDto = ruleDao.getTagByKey(tagDto.getKey()); * <p/> * * FIND Methods * - returns a List of DTO. * - Returns an empty list id no match * - method name is FULLY-NOMINATIVE * - prefixed with DTO type * - examples: * - List<RuleTagDto> tags findRuleTagByRuleKey(RuleKey key) * - List<RuleTagDto> tags findRuleTagByRepositoryAndLanguage(RepositoryKey key, String language) * <p/> * * CRUD Methods are slightly different because they REQUIRE the main DTO to be valid * - Nested dto methods MUST have the Main DTO or it's key as param * - add * - remove * - update * - examples: * - RuleTagDto tag add(RuleTagDto tag, RuleKey key) * - RuleParamDto param add(RuleParamDto param, RuleDto rule) * * @param <MAPPER> iBatis Mapper class * @param <DTO> Produced DTO class from this dao * @param <KEY> DTO Key class */ public abstract class BaseDao<MAPPER, DTO extends Dto<KEY>, KEY extends Serializable> implements Dao<DTO, KEY>, DaoComponent { private static final Logger LOGGER = LoggerFactory.getLogger(BaseDao.class); protected IndexDefinition indexDefinition; private Class<MAPPER> mapperClass; private System2 system2; protected boolean hasIndex() { return indexDefinition != null; } protected BaseDao(@Nullable IndexDefinition indexDefinition, Class<MAPPER> mapperClass, System2 system2) { this.mapperClass = mapperClass; this.indexDefinition = indexDefinition; this.system2 = system2; } protected BaseDao(Class<MAPPER> mapperClass, System2 system2) { this(null, mapperClass, system2); } public String getIndexType() { return indexDefinition != null ? this.indexDefinition.getIndexType() : null; } protected MAPPER mapper(DbSession session) { return session.getMapper(mapperClass); } @Override @CheckForNull public DTO getNullableByKey(DbSession session, KEY key) { return doGetNullableByKey(session, key); } @Override public DTO getByKey(DbSession session, KEY key) { DTO value = doGetNullableByKey(session, key); if (value == null) { throw new NotFoundException(String.format("Key '%s' not found", key)); } return value; } public List<DTO> getByKeys(DbSession session, KEY... keys) { return getByKeys(session, ImmutableList.<KEY>copyOf(keys)); } public List<DTO> getByKeys(DbSession session, Collection<KEY> keys) { if (keys.isEmpty()) { return Collections.emptyList(); } List<DTO> components = newArrayList(); List<List<KEY>> partitionList = Lists.partition(newArrayList(keys), 1000); for (List<KEY> partition : partitionList) { List<DTO> dtos = doGetByKeys(session, partition); components.addAll(dtos); } return components; } protected List<DTO> doGetByKeys(DbSession session, Collection<KEY> keys) { throw notImplemented(this); } @Override public DTO update(DbSession session, DTO item) { Date now = new Date(system2.now()); update(session, item, now); return item; } @Override public DTO update(DbSession session, DTO item, DTO... others) { update(session, Lists.<DTO>asList(item, others)); return item; } @Override public Collection<DTO> update(DbSession session, Collection<DTO> items) { Date now = new Date(system2.now()); for (DTO item : items) { update(session, item, now); } return items; } private void update(DbSession session, DTO item, Date now) { try { item.setUpdatedAt(now); doUpdate(session, item); if (hasIndex()) { session.enqueue(new UpsertDto(getIndexType(), item)); } } catch (Exception e) { throw new IllegalStateException("Fail to update item in db: " + item, e); } } @Override public DTO insert(DbSession session, DTO item) { insert(session, item, new Date(system2.now())); return item; } @Override public Collection<DTO> insert(DbSession session, Collection<DTO> items) { Date now = new Date(system2.now()); for (DTO item : items) { insert(session, item, now); } return items; } @Override public DTO insert(DbSession session, DTO item, DTO... others) { insert(session, Lists.<DTO>asList(item, others)); return item; } private void insert(DbSession session, DTO item, Date now) { if (item.getCreatedAt() == null) { item.setCreatedAt(now); } item.setUpdatedAt(now); try { doInsert(session, item); if (hasIndex()) { session.enqueue(new UpsertDto<DTO>(getIndexType(), item)); } } catch (Exception e) { throw new IllegalStateException("Fail to insert item in db: " + item, e); } } @Override public void delete(DbSession session, DTO item) { deleteByKey(session, item.getKey()); } @Override public void delete(DbSession session, DTO item, DTO... others) { delete(session, Lists.<DTO>asList(item, others)); } @Override public void delete(DbSession session, Collection<DTO> items) { for (DTO item : items) { delete(session, item); } } @Override public void deleteByKey(DbSession session, KEY key) { Preconditions.checkNotNull(key, "Missing key"); try { doDeleteByKey(session, key); if (hasIndex()) { session.enqueue(new DeleteKey<KEY>(getIndexType(), key)); } } catch (Exception e) { throw new IllegalStateException("Fail to delete item from db: " + key, e); } } protected final void enqueueUpdate(Object nestedItem, KEY key, DbSession session) { if (hasIndex()) { session.enqueue(new UpsertNestedItem<KEY>(this.getIndexType(), key, nestedItem)); } } public void enqueueDelete(Object nestedItem, KEY key, DbSession session) { if (hasIndex()) { session.enqueue(new DeleteNestedItem<KEY>(this.getIndexType(), key, nestedItem)); session.commit(); } } public void enqueueInsert(Object nestedItem, KEY key, DbSession session) { if (hasIndex()) { this.enqueueUpdate(nestedItem, key, session); } } @VisibleForTesting public List<DTO> findAfterDate(final DbSession session, Date date, Map<String, String> params) { return session.selectList(getSynchronizeStatementFQN(), getSynchronizationParams(date, params)); } @VisibleForTesting public List<DTO> findAfterDate(final DbSession session, Date date) { return findAfterDate(session, date, Collections.<String, String>emptyMap()); } // Synchronization methods protected DbSynchronizationHandler getSynchronizationResultHandler(final DbSession session, Map<String, String> params) { return new DbSynchronizationHandler(session, params) { private int count = 0; @Override public void handleResult(ResultContext resultContext) { DTO dto = (DTO) resultContext.getResultObject(); // session.enqueue(new UpsertDto<DTO>(getIndexType(), dto, false)); getSession().enqueue(new InsertDto<DTO>(getIndexType(), dto, false)); count++; if (count % 100000 == 0) { LOGGER.info(" - synchronized {} {}", count, getIndexType()); } } @Override public void enqueueCollected() { // Do nothing in this case } }; } protected Map<String, Object> getSynchronizationParams(Date date, Map<String, String> params) { Map<String, Object> finalParams = newHashMap(); finalParams.put("date", new Timestamp(date.getTime())); return finalParams; } @Override public void synchronizeAfter(final DbSession session, Date date) { this.synchronizeAfter(session, date, Collections.<String, String>emptyMap()); } @Override public void synchronizeAfter(final DbSession session, Date date, Map<String, String> params) { if (!session.getClass().isAssignableFrom(BatchSession.class)) { LOGGER.warn("Synchronizer should only be used with BatchSession!"); } DbSynchronizationHandler handler = getSynchronizationResultHandler(session, params); session.select(getSynchronizeStatementFQN(), getSynchronizationParams(date, params), handler); handler.enqueueCollected(); session.enqueue(new RefreshIndex(this.getIndexType())); session.commit(); } private String getSynchronizeStatementFQN() { return mapperClass.getName() + "." + this.getSynchronizationStatementName(); } @CheckForNull protected abstract DTO doGetNullableByKey(DbSession session, KEY key); protected String getSynchronizationStatementName() { return "selectAfterDate"; } protected DTO doInsert(DbSession session, DTO item) { throw notImplemented(this); } protected DTO doUpdate(DbSession session, DTO item) { throw notImplemented(this); } protected void doDeleteByKey(DbSession session, KEY key) { throw notImplemented(this); } private static RuntimeException notImplemented(BaseDao baseDao) { throw new IllegalStateException( "Not implemented yet for class [" + baseDao.getClass().getSimpleName() + "]"); } }