Java tutorial
/* * Copyright 2011-2017 B2i Healthcare Pte Ltd, http://b2i.sg * * 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 com.b2international.snowowl.snomed.datastore.request; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Sets.newHashSet; import java.io.IOException; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import com.b2international.index.Hits; import com.b2international.index.query.Query; import com.b2international.index.revision.RevisionSearcher; import com.b2international.snowowl.core.api.SnowowlRuntimeException; import com.b2international.snowowl.core.domain.BranchContext; import com.b2international.snowowl.core.events.DelegatingRequest; import com.b2international.snowowl.core.events.Request; import com.b2international.snowowl.core.events.bulk.BulkRequest; import com.b2international.snowowl.core.events.metrics.Metrics; import com.b2international.snowowl.core.events.metrics.Timer; import com.b2international.snowowl.core.exceptions.AlreadyExistsException; import com.b2international.snowowl.core.exceptions.BadRequestException; import com.b2international.snowowl.core.exceptions.NotImplementedException; import com.b2international.snowowl.core.terminology.ComponentCategory; import com.b2international.snowowl.datastore.index.RevisionDocument; import com.b2international.snowowl.datastore.request.TransactionalRequest; import com.b2international.snowowl.snomed.core.domain.BranchMetadataResolver; import com.b2international.snowowl.snomed.core.domain.ConstantIdStrategy; import com.b2international.snowowl.snomed.core.domain.MetadataIdStrategy; import com.b2international.snowowl.snomed.core.domain.NamespaceIdStrategy; import com.b2international.snowowl.snomed.datastore.config.SnomedCoreConfiguration; import com.b2international.snowowl.snomed.datastore.id.SnomedIdentifiers; import com.b2international.snowowl.snomed.datastore.id.action.IdActionRecorder; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedComponentDocument; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedConceptDocument; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedDescriptionIndexEntry; import com.b2international.snowowl.snomed.datastore.index.entry.SnomedRelationshipIndexEntry; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterators; import com.google.common.collect.Multimap; /** * @since 4.5 */ final class IdRequest<C extends BranchContext, R> extends DelegatingRequest<C, C, R> { /** * The maximum number of identifier service reservation calls (after which a namespace is known to be completely full). */ public static final int ID_GENERATION_ATTEMPTS = 9999_9999; private static final long serialVersionUID = 1L; private static final Map<ComponentCategory, Class<? extends SnomedComponentDocument>> CATEGORY_TO_DOCUMENT_CLASS_MAP; static { CATEGORY_TO_DOCUMENT_CLASS_MAP = ImmutableMap .<ComponentCategory, Class<? extends SnomedComponentDocument>>builder() .put(ComponentCategory.CONCEPT, SnomedConceptDocument.class) .put(ComponentCategory.RELATIONSHIP, SnomedRelationshipIndexEntry.class) .put(ComponentCategory.DESCRIPTION, SnomedDescriptionIndexEntry.class).build(); } private static Class<? extends SnomedComponentDocument> getDocumentClass(final ComponentCategory category) { return CATEGORY_TO_DOCUMENT_CLASS_MAP.get(category); } protected IdRequest(final Request<C, R> next) { super(next); } @Override public R execute(final C context) { final IdActionRecorder recorder = new IdActionRecorder(context); try { final Multimap<ComponentCategory, SnomedComponentCreateRequest> componentCreateRequests = getComponentCreateRequests( next()); if (!componentCreateRequests.isEmpty()) { final Timer idGenerationTimer = context.service(Metrics.class).timer("idGeneration"); try { idGenerationTimer.start(); for (final ComponentCategory category : componentCreateRequests.keySet()) { final Class<? extends SnomedComponentDocument> documentClass = getDocumentClass(category); final Collection<BaseSnomedComponentCreateRequest> categoryRequests = FluentIterable .from(componentCreateRequests.get(category)) .filter(BaseSnomedComponentCreateRequest.class).toSet(); final Set<String> userSuppliedIds = FluentIterable.from(categoryRequests) .filter(request -> request.getIdGenerationStrategy() instanceof ConstantIdStrategy) .transform( request -> ((ConstantIdStrategy) request.getIdGenerationStrategy()).getId()) .toSet(); if (!userSuppliedIds.isEmpty()) { final Set<String> existingIds = getExistingIds(context, userSuppliedIds, documentClass); if (!existingIds.isEmpty()) { throw new AlreadyExistsException(category.getDisplayName(), existingIds); } recorder.register(userSuppliedIds); } final Multimap<String, BaseSnomedComponentCreateRequest> requestsByNamespace = HashMultimap .create(); FluentIterable.from(categoryRequests) .filter(request -> request.getIdGenerationStrategy() instanceof NamespaceIdStrategy) .forEach(request -> { NamespaceIdStrategy strategy = (NamespaceIdStrategy) request .getIdGenerationStrategy(); String namespace = Strings.isNullOrEmpty(strategy.getNamespace()) || SnomedIdentifiers.INT_NAMESPACE.equals(strategy.getNamespace()) ? SnomedIdentifiers.INT_NAMESPACE : strategy.getNamespace(); requestsByNamespace.put(namespace, request); }); Set<BaseSnomedComponentCreateRequest> metadataBasedIdRequests = FluentIterable .from(categoryRequests) .filter(request -> request.getIdGenerationStrategy() instanceof MetadataIdStrategy) .toSet(); if (!metadataBasedIdRequests.isEmpty()) { String namespaceFromMetadata = BranchMetadataResolver.getEffectiveBranchMetadataValue( context.branch(), SnomedCoreConfiguration.BRANCH_DEFAULT_NAMESPACE_KEY); String namespace = Strings.isNullOrEmpty(namespaceFromMetadata) ? SnomedIdentifiers.INT_NAMESPACE : namespaceFromMetadata; metadataBasedIdRequests.forEach(request -> requestsByNamespace.put(namespace, request)); } for (final String namespace : requestsByNamespace.keySet()) { final String convertedNamespace = namespace.equals(SnomedIdentifiers.INT_NAMESPACE) ? null : namespace; final Collection<BaseSnomedComponentCreateRequest> requests = requestsByNamespace .get(namespace); final int count = requests.size(); final Set<String> uniqueIds = getUniqueIds(context, recorder, category, documentClass, count, convertedNamespace); final Iterator<String> idsToUse = Iterators.consumingIterator(uniqueIds.iterator()); for (final BaseSnomedComponentCreateRequest createRequest : requests) { createRequest.setIdGenerationStrategy(new ConstantIdStrategy(idsToUse.next())); } checkState(!idsToUse.hasNext(), "More SNOMED CT ids have been requested than used."); } } } finally { idGenerationTimer.stop(); } } final R commitInfo = next(context); recorder.commit(); return commitInfo; } catch (final Exception e) { recorder.rollback(); throw e; } } private static Multimap<ComponentCategory, SnomedComponentCreateRequest> getComponentCreateRequests( final Request<?, ?> request) { final ImmutableMultimap.Builder<ComponentCategory, SnomedComponentCreateRequest> resultBuilder = ImmutableMultimap .builder(); collectComponentCreateRequests(request, resultBuilder); return resultBuilder.build(); } private static void collectComponentCreateRequests(Request<?, ?> request, ImmutableMultimap.Builder<ComponentCategory, SnomedComponentCreateRequest> resultBuilder) { if (request instanceof DelegatingRequest) { collectComponentCreateRequests(((DelegatingRequest<?, ?, ?>) request).next(), resultBuilder); } else if (request instanceof TransactionalRequest) { collectComponentCreateRequests(((TransactionalRequest) request).getNext(), resultBuilder); } else if (request instanceof BaseSnomedComponentCreateRequest) { final BaseSnomedComponentCreateRequest createRequest = (BaseSnomedComponentCreateRequest) request; for (SnomedCoreComponentCreateRequest nestedRequest : createRequest.getNestedRequests()) { ComponentCategory category = getComponentCategory(nestedRequest); resultBuilder.put(category, (BaseSnomedComponentCreateRequest) nestedRequest); // XXX: we could recurse here, but only concept creation requests have actual nested requests at the moment } } else if (request instanceof BulkRequest) { final BulkRequest<?> bulkRequest = (BulkRequest<?>) request; for (Request<?, ?> bulkRequestItem : bulkRequest.getRequests()) { collectComponentCreateRequests(bulkRequestItem, resultBuilder); } } } private static ComponentCategory getComponentCategory(SnomedComponentRequest<?> request) { if (request instanceof SnomedConceptCreateRequest) { return ComponentCategory.CONCEPT; } else if (request instanceof SnomedDescriptionCreateRequest) { return ComponentCategory.DESCRIPTION; } else if (request instanceof SnomedRelationshipCreateRequest) { return ComponentCategory.RELATIONSHIP; } else { throw new NotImplementedException("Unknown create request type: %s", request.getClass().getName()); } } private Set<String> getUniqueIds(final BranchContext context, final IdActionRecorder recorder, final ComponentCategory category, final Class<? extends SnomedComponentDocument> documentClass, final int quantity, final String namespace) { final Set<String> uniqueIds = newHashSet(); for (int i = 0; i < ID_GENERATION_ATTEMPTS && uniqueIds.size() < quantity; i++) { final Set<String> candidateIds = recorder.reserve(namespace, category, quantity - uniqueIds.size()); uniqueIds.addAll(candidateIds); final Set<String> existingIds = getExistingIds(context, candidateIds, documentClass); uniqueIds.removeAll(existingIds); } if (uniqueIds.size() == quantity) { return uniqueIds; } else { throw new BadRequestException( "There are insufficient number of component ids available for category: %s", category.getDisplayName()); } } private Set<String> getExistingIds(final BranchContext context, final Set<String> ids, final Class<? extends SnomedComponentDocument> documentClass) { try { final Query<? extends SnomedComponentDocument> getComponentsById = Query.select(documentClass) .fields(RevisionDocument.Fields.ID).where(RevisionDocument.Expressions.ids(ids)) .limit(ids.size()).build(); final Hits<? extends SnomedComponentDocument> hits = context.service(RevisionSearcher.class) .search(getComponentsById); return FluentIterable.from(hits).transform(SnomedComponentDocument::getId).toSet(); } catch (IOException e) { throw new SnowowlRuntimeException(e); } } }