protected void addRepositoryDetails( Request request, SearchNGResponse response, Repository repository) { boolean add = true; for (NexusNGRepositoryDetail repoDetail : response.getRepoDetails()) { if (repoDetail.getRepositoryId().equals(repository.getId())) { add = false; break; } } if (add) { NexusNGRepositoryDetail repoDetail = new NexusNGRepositoryDetail(); repoDetail.setRepositoryId(repository.getId()); repoDetail.setRepositoryName(repository.getName()); repoDetail.setRepositoryURL( createRepositoryReference(request, repository.getId()).getTargetRef().toString()); repoDetail.setRepositoryContentClass(repository.getRepositoryContentClass().getId()); repoDetail.setRepositoryKind(extractRepositoryKind(repository)); MavenRepository mavenRepo = repository.adaptToFacet(MavenRepository.class); if (mavenRepo != null) { repoDetail.setRepositoryPolicy(mavenRepo.getRepositoryPolicy().name()); } response.addRepoDetail(repoDetail); } }
protected void repackIteratorSearchResponse( Request request, Map<String, String> terms, SearchNGResponse response, boolean collapsed, Integer from, int count, IteratorSearchResponse iterator, SystemWideLatestVersionCollector systemWideCollector, RepositoryWideLatestVersionCollector repositoryWideCollector) throws NoSuchRepositoryException, IOException { response.setCollapsed(collapsed); response.setTotalCount(iterator.getTotalHitsCount()); response.setFrom(from == null ? -1 : from.intValue()); response.setCount(count == LUCENE_HIT_LIMIT ? -1 : count); // System.out.println( "** Query is \"" + iterator.getQuery().toString() + "\"." ); try { if (!response.isTooManyResults()) { // 1st pass, collect results LinkedHashMap<String, NexusNGArtifact> hits = new LinkedHashMap<String, NexusNGArtifact>(); NexusNGArtifact artifact; float firstDocumentScore = -1f; float lastDocumentScore = -1f; final long startedAtMillis = System.currentTimeMillis(); // 1sd pass, build first two level (no links), and actually consume the iterator and // collectors will be // set for (ArtifactInfo ai : iterator) { final String key = ai.groupId + ":" + ai.artifactId + ":" + ai.version; artifact = hits.get(key); // System.out.println( "* " + ai.context + " : " + ai.toString() + " -- " + // ai.getLuceneScore() + // " -- " // + ( artifact != null ? "F" : "N" ) ); if (artifact == null) { if (System.currentTimeMillis() - startedAtMillis > FIRST_LOOP_EXECUTION_TIME_LIMIT) { getSearchDiagnosticLogger() .debug( "Stopping delivering search results since we spent more than " + FIRST_LOOP_EXECUTION_TIME_LIMIT + " millis in 1st loop processing results."); break; } // we stop if we delivered "most important" hits (change of relevance from 1st document // we got) if (hits.size() > 10 && (firstDocumentScore - ai.getLuceneScore()) > DOCUMENT_TOP_RELEVANCE_HIT_CHANGE_THRESHOLD) { getSearchDiagnosticLogger() .debug( "Stopping delivering search results since we span " + DOCUMENT_TOP_RELEVANCE_HIT_CHANGE_THRESHOLD + " of score change (firstDocScore=" + firstDocumentScore + ", currentDocScore=" + ai.getLuceneScore() + ")."); break; } // we stop if we detect a "big drop" in relevance in relation to previous document's // score if (hits.size() > 10 && lastDocumentScore > 0) { if ((lastDocumentScore - ai.getLuceneScore()) > DOCUMENT_RELEVANCE_HIT_CHANGE_THRESHOLD) { getSearchDiagnosticLogger() .debug( "Stopping delivering search results since we hit a relevance drop bigger than " + DOCUMENT_RELEVANCE_HIT_CHANGE_THRESHOLD + " (lastDocScore=" + lastDocumentScore + ", currentDocScore=" + ai.getLuceneScore() + ")."); // the relevance change was big, so we stepped over "trash" results that are // probably not relevant at all, just stop here then break; } } // we stop if we hit the GA limit if ((hits.size() + 1) > GA_HIT_LIMIT) { getSearchDiagnosticLogger() .debug( "Stopping delivering search results since we hit a GA hit limit of " + GA_HIT_LIMIT + "."); // check for HIT_LIMIT: if we are stepping it over, stop here break; } else { artifact = new NexusNGArtifact(); artifact.setGroupId(ai.groupId); artifact.setArtifactId(ai.artifactId); artifact.setVersion(ai.version); artifact.setHighlightedFragment(getMatchHighlightHtmlSnippet(ai)); hits.put(key, artifact); } } Repository repository = getUnprotectedRepositoryRegistry().getRepository(ai.repository); addRepositoryDetails(request, response, repository); NexusNGArtifactHit hit = null; for (NexusNGArtifactHit artifactHit : artifact.getArtifactHits()) { if (repository.getId().equals(artifactHit.getRepositoryId())) { hit = artifactHit; break; } } if (hit == null) { hit = new NexusNGArtifactHit(); hit.setRepositoryId(repository.getId()); // if collapsed, we add links in 2nd pass, otherwise here if (!collapsed) { // we are adding the POM link "blindly", unless packaging is POM, // since the it will be added below the "usual" way if (!"pom".equals(ai.packaging)) { NexusNGArtifactLink link = createNexusNGArtifactLink( request, ai.repository, ai.groupId, ai.artifactId, ai.version, "pom", null); // add the POM link hit.addArtifactLink(link); } } // we just created it, add it artifact.addArtifactHit(hit); } if (!collapsed) { boolean needsToBeAdded = true; for (NexusNGArtifactLink link : hit.getArtifactLinks()) { if (StringUtils.equals(link.getClassifier(), ai.classifier) && StringUtils.equals(link.getExtension(), ai.fextension)) { needsToBeAdded = false; break; } } if (needsToBeAdded) { NexusNGArtifactLink link = createNexusNGArtifactLink( request, ai.repository, ai.groupId, ai.artifactId, ai.version, ai.fextension, ai.classifier); hit.addArtifactLink(link); } } if (firstDocumentScore < 0) { firstDocumentScore = ai.getLuceneScore(); } lastDocumentScore = ai.getLuceneScore(); } // summary: getSearchDiagnosticLogger() .debug( "Query terms \"" + terms + "\" (LQL \"" + iterator.getQuery() + "\") matched total of " + iterator.getTotalHitsCount() + " records, " + iterator.getTotalProcessedArtifactInfoCount() + " records were processed out of those, resulting in " + hits.size() + " unique GA records. Lucene scored documents first=" + firstDocumentScore + ", last=" + lastDocumentScore + ". Main processing loop took " + (System.currentTimeMillis() - startedAtMillis) + " ms."); // 2nd pass, set versions for (NexusNGArtifact artifactNg : hits.values()) { final String systemWideCollectorKey = systemWideCollector.getKey(artifactNg.getGroupId(), artifactNg.getArtifactId()); LatestVersionHolder systemWideHolder = systemWideCollector.getLVHForKey(systemWideCollectorKey); if (systemWideHolder != null) { if (systemWideHolder.getLatestSnapshot() != null) { artifactNg.setLatestSnapshot(systemWideHolder.getLatestSnapshot().toString()); artifactNg.setLatestSnapshotRepositoryId( systemWideHolder.getLatestSnapshotRepositoryId()); } if (systemWideHolder.getLatestRelease() != null) { artifactNg.setLatestRelease(systemWideHolder.getLatestRelease().toString()); artifactNg.setLatestReleaseRepositoryId( systemWideHolder.getLatestReleaseRepositoryId()); } } // add some "touche" on 1st level if (collapsed) { // set the top level version to one of the latest ones if (artifactNg.getLatestRelease() != null) { artifactNg.setVersion(artifactNg.getLatestRelease()); } else { artifactNg.setVersion(artifactNg.getLatestSnapshot()); } // "create" the links now for (NexusNGArtifactHit hit : artifactNg.getArtifactHits()) { final String repositoryWideCollectorKey = repositoryWideCollector.getKey( hit.getRepositoryId(), artifactNg.getGroupId(), artifactNg.getArtifactId()); LatestECVersionHolder repositoryWideHolder = repositoryWideCollector.getLVHForKey(repositoryWideCollectorKey); if (repositoryWideHolder != null) { String versionToSet = null; // do we have a "latest release" version? if (repositoryWideHolder.getLatestRelease() != null) { versionToSet = repositoryWideHolder.getLatestRelease().toString(); } else { versionToSet = repositoryWideHolder.getLatestSnapshot().toString(); } // add POM link NexusNGArtifactLink pomLink = createNexusNGArtifactLink( request, hit.getRepositoryId(), artifactNg.getGroupId(), artifactNg.getArtifactId(), versionToSet, "pom", null); hit.addArtifactLink(pomLink); // TODO: order! // add main artifact link // add everything else // make the list by joining two collections // rationale: in case of reposes, only one of these will be populated, other will be // empty // but in case of mixed policy (like group), probably both will exist // TODO: this will not work like it in groups, since then the versions will // mismatch! ArrayList<ECHolder> ecHolders = new ArrayList<ECHolder>(repositoryWideHolder.getReleaseECHolders()); ecHolders.addAll(repositoryWideHolder.getSnapshotECHolders()); for (ECHolder holder : ecHolders) { // add non-poms only, since we added POMs above if (!"pom".equals(holder.getExtension())) { NexusNGArtifactLink link = createNexusNGArtifactLink( request, hit.getRepositoryId(), artifactNg.getGroupId(), artifactNg.getArtifactId(), versionToSet, holder.getExtension(), holder.getClassifier()); hit.addArtifactLink(link); } } } } } } response.setData(new ArrayList<NexusNGArtifact>(hits.values())); } } finally { iterator.close(); } response.setTooManyResults(iterator.getTotalHitsCount() > count); }
/** * Search against all repositories using provided parameters. Note there are a few different types * of searches you can perform. If you provide the 'q' query parameter, a keyword search will be * performed. If you provide the 'g, a, v, p or c' query parameters, a maven coordinate search * will be performed. If you provide the 'cn' query parameter, a classname search will be * performed. If you provide the 'sha1' query parameter, a checksum search will be performed. * * @param q provide this param for a keyword search (g, a, v, p, c, cn, sha1 params will be * ignored). * @param sha1 provide this param for a checksum search (g, a, v, p, c, cn params will be * ignored). * @param cn provide this param for a classname search (g, a, v, p, c params will be ignored). * @param g group id to perform a maven search against (can be combined with a, v, p & c params as * well). * @param a artifact id to perform a maven search against (can be combined with g, v, p & c params * as well). * @param v version to perform a maven search against (can be combined with g, a, p & c params as * well). * @param p packaging type to perform a maven search against (can be combined with g, a, v & c * params as well). * @param c classifier to perform a maven search against (can be combined with g, a, v & p params * as well). * @param from result index to start retrieving results from. * @param count number of results to have returned to you. * @param repositoryId The repositoryId to which repository search should be narrowed. Omit if * search should be global. */ @Override @GET @ResourceMethodSignature( queryParams = { @QueryParam("q"), @QueryParam("g"), @QueryParam("a"), @QueryParam("v"), @QueryParam("p"), @QueryParam("c"), @QueryParam("cn"), @QueryParam("sha1"), @QueryParam("from"), @QueryParam("count"), @QueryParam("repositoryId") }, output = SearchResponse.class) public Object get(Context context, Request request, Response response, Variant variant) throws ResourceException { Form form = request.getResourceRef().getQueryAsForm(); final Map<String, String> terms = new HashMap<String, String>(); for (Parameter parameter : form) { terms.put(parameter.getName(), parameter.getValue()); } Integer from = null; Boolean exact = null; String repositoryId = null; Boolean expandVersion = Boolean.FALSE; Boolean collapseResults = Boolean.FALSE; if (form.getFirstValue("from") != null) { try { from = Integer.valueOf(form.getFirstValue("from")); } catch (NumberFormatException e) { from = null; } } int count = LUCENE_HIT_LIMIT; if (form.getFirstValue("count") != null) { try { // capping the possible count count = Math.min(LUCENE_HIT_LIMIT, Integer.valueOf(form.getFirstValue("count"))); } catch (NumberFormatException e) { count = LUCENE_HIT_LIMIT; } } if (form.getFirstValue("repositoryId") != null) { repositoryId = form.getFirstValue("repositoryId"); } if (form.getFirstValue("exact") != null) { exact = Boolean.valueOf(form.getFirstValue("exact")); } if (form.getFirstValue("versionexpand") != null) { expandVersion = Boolean.valueOf(form.getFirstValue("versionexpand")); } if (form.getFirstValue("collapseresults") != null) { collapseResults = Boolean.valueOf(form.getFirstValue("collapseresults")); } // A little explanation about collapseResults, that might seems little bit awkward, since // currently we have only // one column "collapsable" (the version), but before and maybe in the future that's not the // case. So, here is // it: // the "collapseResults" is just a flag "do we allow collapse at all". It is just a shorthand to // turn on or off // collapse generally // Let's assume we have columns colA, colB and colC collapsable. So, instead saying // expandColA=true,expandColB=true,expandColC=true, // it is just easy to say collapseresults=false // BUT, if collapseresults=true is sent by client, even then an "override" will happen if there // is actually NO // ROW to collapse! // So: collapseresults=false EQUALS-TO expandColA=true & expandColB=true & expandColC=true if (collapseResults) { // here we would like to have ANDed all the collapsable column flags and negated the result // currently we collapseResults = !(true && expandVersion); // && expandColA && expandColB; } IteratorSearchResponse searchResult = null; SearchNGResponse result = new SearchNGResponse(); int runCount = 0; while (runCount < RETRIES) { try { List<ArtifactInfoFilter> filters = new ArrayList<ArtifactInfoFilter>(); // we need to save this reference to later SystemWideLatestVersionCollector systemWideCollector = new SystemWideLatestVersionCollector(); filters.add(systemWideCollector); RepositoryWideLatestVersionCollector repositoryWideCollector = null; if (collapseResults) { repositoryWideCollector = new RepositoryWideLatestVersionCollector(); filters.add(repositoryWideCollector); } try { searchResult = searchByTerms( terms, repositoryId, from, count, exact, expandVersion, collapseResults, filters, searchers); if (searchResult == null) { collapseResults = false; continue; } else { repackIteratorSearchResponse( request, terms, result, collapseResults, from, count, searchResult, systemWideCollector, repositoryWideCollector); if (!result.isTooManyResults()) { // if we had collapseResults ON, and the totalHits are larger than actual (filtered) // results, and the actual result count is below COLLAPSE_OVERRIDE_TRESHOLD, // and full result set is smaller than HIT_LIMIT // then repeat without collapse if (collapseResults && result.getData().size() < searchResult.getTotalHitsCount() && result.getData().size() < COLLAPSE_OVERRIDE_TRESHOLD && searchResult.getTotalHitsCount() < GA_HIT_LIMIT) { collapseResults = false; continue; } } } } catch (IOException e) { throw new ResourceException(Status.SERVER_ERROR_INTERNAL, e.getMessage(), e); } // we came here, so we break the while-loop, we got what we need break; } catch (NoSuchRepositoryException e) { throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, "Repository to be searched does not exists!", e); } catch (AlreadyClosedException e) { runCount++; getLogger() .info( "NexusIndexer issue (NEXUS-3702), we got AlreadyClosedException that happens when Reindexing or other \"indexer intensive\" task is running on instance while searching! Redoing search again."); if (getLogger().isDebugEnabled()) { // just keep it silent (DEBUG) getLogger().debug("Got AlreadyClosedException exception!", e); } result.setData(null); } } if (result.getData() == null) { try { repackIteratorSearchResponse( request, terms, result, collapseResults, from, count, IteratorSearchResponse.empty(null), null, null); } catch (NoSuchRepositoryException e) { // will not happen } catch (IOException e) { // will not happen } getLogger() .info( "Nexus issue (NEXUS-3702): Was unable to perform search " + RETRIES + " times, giving up, and lying about TooManyResults. Please retry to reproduce this with DEBUG logs and report this issue!"); } return result; }