/* * Copyright 2012 - 2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.solr.repository.support; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Optional; import org.apache.solr.common.SolrInputDocument; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.solr.core.SolrOperations; import org.springframework.data.solr.core.SolrTransactionSynchronizationAdapterBuilder; import org.springframework.data.solr.core.mapping.SimpleSolrMappingContext; import org.springframework.data.solr.core.query.Criteria; import org.springframework.data.solr.core.query.SimpleFilterQuery; import org.springframework.data.solr.core.query.SimpleQuery; import org.springframework.data.solr.core.query.SolrPageRequest; import org.springframework.data.solr.repository.SolrCrudRepository; import org.springframework.data.solr.repository.query.SolrEntityInformation; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; /** * Solr specific repository implementation. Likely to be used as target within {@link SolrRepositoryFactory} * * @param <T> * @Param <ID> * @author Christoph Strobl */ public class SimpleSolrRepository<T, ID extends Serializable> implements SolrCrudRepository<T, ID> { private static final String DEFAULT_ID_FIELD = "id"; private final SolrOperations solrOperations; private String idFieldName = DEFAULT_ID_FIELD; private final Class<T> entityClass; private final String solrCollectionName; private final SolrEntityInformation<T, ?> entityInformation; /** * @param metadata must not be null * @param solrOperations must not be null */ public SimpleSolrRepository(SolrOperations solrOperations, SolrEntityInformation<T, ?> metadata) { Assert.notNull(metadata, "Metadata must not be null!"); this.solrOperations = solrOperations; this.entityInformation = metadata; this.entityClass = this.entityInformation.getJavaType(); this.idFieldName = this.entityInformation.getIdAttribute(); this.solrCollectionName = this.entityInformation.getSolrCoreName(); } /** * @param solrOperations must not be null * @param entityClass */ public SimpleSolrRepository(SolrOperations solrOperations, Class<T> entityClass) { this(solrOperations, getEntityInformation(entityClass)); } private static SolrEntityInformation getEntityInformation(Class type) { return new SolrEntityInformationCreatorImpl(new SimpleSolrMappingContext()).getEntityInformation(type); } @Override public Optional<T> findById(ID id) { return getSolrOperations().queryForObject(new SimpleQuery(new Criteria(this.idFieldName).is(id)), getEntityClass()); } @Override public Iterable<T> findAll() { int itemCount = (int) this.count(); if (itemCount == 0) { return new PageImpl<>(Collections.<T> emptyList()); } return this.findAll(new SolrPageRequest(0, itemCount)); } @Override public Page<T> findAll(Pageable pageable) { return getSolrOperations().queryForPage(solrCollectionName, new SimpleQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD)).setPageRequest(pageable), getEntityClass()); } @Override public Iterable<T> findAll(Sort sort) { int itemCount = (int) this.count(); if (itemCount == 0) { return new PageImpl<>(Collections.<T> emptyList()); } return getSolrOperations().queryForPage(solrCollectionName, new SimpleQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD)) .setPageRequest(new SolrPageRequest(0, itemCount)).addSort(sort), getEntityClass()); } @Override public Iterable<T> findAllById(Iterable<ID> ids) { org.springframework.data.solr.core.query.Query query = new SimpleQuery(new Criteria(this.idFieldName).in(ids)); query.setPageRequest(new SolrPageRequest(0, (int) count(query))); return getSolrOperations().queryForPage(solrCollectionName, query, getEntityClass()); } @Override public long count() { return count(new SimpleQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD))); } protected long count(org.springframework.data.solr.core.query.Query query) { org.springframework.data.solr.core.query.Query countQuery = SimpleQuery.fromQuery(query); return getSolrOperations().count(solrCollectionName, countQuery); } @Override public <S extends T> S save(S entity) { Assert.notNull(entity, "Cannot save 'null' entity."); registerTransactionSynchronisationIfSynchronisationActive(); this.solrOperations.saveBean(solrCollectionName, entity); commitIfTransactionSynchronisationIsInactive(); return entity; } @Override public <S extends T> Iterable<S> saveAll(Iterable<S> entities) { Assert.notNull(entities, "Cannot insert 'null' as a List."); if (!(entities instanceof Collection<?>)) { throw new InvalidDataAccessApiUsageException("Entities have to be inside a collection"); } registerTransactionSynchronisationIfSynchronisationActive(); this.solrOperations.saveBeans(solrCollectionName, (Collection<? extends T>) entities); commitIfTransactionSynchronisationIsInactive(); return entities; } @Override public boolean existsById(ID id) { return findById(id).isPresent(); } @Override public void deleteById(ID id) { Assert.notNull(id, "Cannot delete entity with id 'null'."); registerTransactionSynchronisationIfSynchronisationActive(); this.solrOperations.deleteById(solrCollectionName, id.toString()); commitIfTransactionSynchronisationIsInactive(); } @Override public void delete(T entity) { Assert.notNull(entity, "Cannot delete 'null' entity."); deleteAll(Collections.singletonList(entity)); } @Override public void deleteAll(Iterable<? extends T> entities) { Assert.notNull(entities, "Cannot delete 'null' list."); ArrayList<String> idsToDelete = new ArrayList<>(); for (T entity : entities) { idsToDelete.add(extractIdFromBean(entity).toString()); } registerTransactionSynchronisationIfSynchronisationActive(); this.solrOperations.deleteById(solrCollectionName, idsToDelete); commitIfTransactionSynchronisationIsInactive(); } @Override public void deleteAll() { registerTransactionSynchronisationIfSynchronisationActive(); this.solrOperations.delete(solrCollectionName, new SimpleFilterQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD))); commitIfTransactionSynchronisationIsInactive(); } public final String getIdFieldName() { return idFieldName; } public Class<T> getEntityClass() { if (!isEntityClassSet()) { throw new InvalidDataAccessApiUsageException("Unable to resolve EntityClass. Please use according setter!"); } return entityClass; } private boolean isEntityClassSet() { return entityClass != null; } public final SolrOperations getSolrOperations() { return solrOperations; } private Object extractIdFromBean(T entity) { if (entityInformation != null) { return entityInformation.getId(entity).orElseThrow(() -> new IllegalArgumentException("Id must not be null")); } SolrInputDocument solrInputDocument = this.solrOperations.convertBeanToSolrInputDocument(entity); return extractIdFromSolrInputDocument(solrInputDocument); } private String extractIdFromSolrInputDocument(SolrInputDocument solrInputDocument) { Assert.notNull(solrInputDocument.getField(idFieldName), "Unable to find field '" + idFieldName + "' in SolrDocument."); Assert.notNull(solrInputDocument.getField(idFieldName).getValue(), "ID must not be 'null'."); return solrInputDocument.getField(idFieldName).getValue().toString(); } private void registerTransactionSynchronisationIfSynchronisationActive() { if (TransactionSynchronizationManager.isSynchronizationActive()) { registerTransactionSynchronisationAdapter(); } } private void registerTransactionSynchronisationAdapter() { TransactionSynchronizationManager.registerSynchronization(SolrTransactionSynchronizationAdapterBuilder .forOperations(this.solrOperations).onCollection(solrCollectionName).withDefaultBehaviour()); } private void commitIfTransactionSynchronisationIsInactive() { if (!TransactionSynchronizationManager.isSynchronizationActive()) { this.solrOperations.commit(solrCollectionName); } } }