blob: 2320a67c8c7ab898ddfab8dc61e5ab5b86c4a8da [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.index.treeview;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.maven.index.ArtifactInfo;
import org.apache.maven.index.Field;
import org.apache.maven.index.Indexer;
import org.apache.maven.index.IteratorSearchRequest;
import org.apache.maven.index.IteratorSearchResponse;
import org.apache.maven.index.MAVEN;
import org.apache.maven.index.expr.SourcedSearchExpression;
import org.apache.maven.index.treeview.TreeNode.Type;
import org.codehaus.plexus.util.StringUtils;
@Singleton
@Named
public class DefaultIndexTreeView implements IndexTreeView {
private final Indexer indexer;
@Inject
public DefaultIndexTreeView(Indexer indexer) {
this.indexer = indexer;
}
protected Indexer getIndexer() {
return indexer;
}
public TreeNode listNodes(TreeViewRequest request) throws IOException {
// get the last path elem
String name;
if (!"/".equals(request.getPath())) {
if (request.getPath().endsWith("/")) {
name = request.getPath().substring(0, request.getPath().length() - 1);
} else {
name = request.getPath();
}
name = name.substring(name.lastIndexOf('/') + 1);
// root is "/"
if (!name.equals("/") && name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
} else {
name = "/";
}
// the root node depends on request we have, so let's see
TreeNode result = request.getFactory().createGNode(this, request, request.getPath(), name);
if (request.hasFieldHints()) {
listChildren(result, request, null);
} else {
// non hinted way, the "old" way
if ("/".equals(request.getPath())) {
// get root groups and finish
Set<String> rootGroups = request.getIndexingContext().getRootGroups();
for (String group : rootGroups) {
if (group.length() > 0) {
result.getChildren()
.add(request.getFactory()
.createGNode(this, request, request.getPath() + group + "/", group));
}
}
} else {
Set<String> allGroups = request.getIndexingContext().getAllGroups();
listChildren(result, request, allGroups);
}
}
return result;
}
/**
* @param root
* @param request
* @param allGroups
* @throws IOException
*/
protected void listChildren(TreeNode root, TreeViewRequest request, Set<String> allGroups) throws IOException {
String path = root.getPath();
Map<String, TreeNode> folders = new HashMap<>();
String rootPartialGroupId = StringUtils.strip(root.getPath().replaceAll("/", "."), ".");
folders.put(Type.G + ":" + rootPartialGroupId, root);
try (IteratorSearchResponse artifacts = getArtifacts(root, request)) {
for (ArtifactInfo ai : artifacts) {
String versionKey = Type.V + ":" + ai.getArtifactId() + ":" + ai.getVersion();
TreeNode versionResource = folders.get(versionKey);
if (versionResource == null) {
String artifactKey = Type.A + ":" + ai.getArtifactId();
TreeNode artifactResource = folders.get(artifactKey);
if (artifactResource == null) {
TreeNode groupParentResource = root;
TreeNode groupResource;
// here comes the twist: we have to search for parent G node
String partialGroupId = null;
String[] groupIdElems = ai.getGroupId().split("\\.");
for (String groupIdElem : groupIdElems) {
if (partialGroupId == null) {
partialGroupId = groupIdElem;
} else {
partialGroupId = partialGroupId + "." + groupIdElem;
}
String groupKey = Type.G + ":" + partialGroupId;
groupResource = folders.get(groupKey);
// it needs to be created only if not found (is null) and is _below_ groupParentResource
if (groupResource == null
&& groupParentResource.getPath().length()
< getPathForAi(ai, MAVEN.GROUP_ID).length()) {
String gNodeName = partialGroupId.lastIndexOf('.') > -1
? partialGroupId.substring(partialGroupId.lastIndexOf('.') + 1)
: partialGroupId;
groupResource = request.getFactory()
.createGNode(
this,
request,
"/" + partialGroupId.replaceAll("\\.", "/") + "/",
gNodeName);
groupParentResource.getChildren().add(groupResource);
folders.put(groupKey, groupResource);
groupParentResource = groupResource;
} else if (groupResource != null) {
// we found it as already existing, break if this is the node we want
if (groupResource.getPath().equals(getPathForAi(ai, MAVEN.GROUP_ID))) {
break;
}
groupParentResource = groupResource;
}
}
artifactResource = request.getFactory()
.createANode(this, request, ai, getPathForAi(ai, MAVEN.ARTIFACT_ID));
groupParentResource.getChildren().add(artifactResource);
folders.put(artifactKey, artifactResource);
}
versionResource =
request.getFactory().createVNode(this, request, ai, getPathForAi(ai, MAVEN.VERSION));
artifactResource.getChildren().add(versionResource);
folders.put(versionKey, versionResource);
}
String nodePath = getPathForAi(ai, null);
versionResource.getChildren().add(request.getFactory().createArtifactNode(this, request, ai, nodePath));
}
}
if (!request.hasFieldHints()) {
Set<String> groups = getGroups(path, allGroups);
for (String group : groups) {
TreeNode groupResource = root.findChildByPath(path + group + "/", Type.G);
if (groupResource == null) {
groupResource = request.getFactory().createGNode(this, request, path + group + "/", group);
root.getChildren().add(groupResource);
} else {
// if the folder has been created as an artifact name,
// we need to check for possible nested groups as well
listChildren(groupResource, request, allGroups);
}
}
}
}
/**
* Builds a path out from ArtifactInfo. The field parameter controls "how deep" the path goes. Possible values are
* MAVEN.GROUP_ID (builds a path from groupId only), MAVEN.ARTIFACT_ID (builds a path from groupId + artifactId),
* MAVEN.VERSION (builds a path up to version) or anything else (including null) will build "full" artifact path.
*
* @param ai
* @param field
* @return path
*/
protected String getPathForAi(ArtifactInfo ai, Field field) {
StringBuilder sb = new StringBuilder("/");
sb.append(ai.getGroupId().replaceAll("\\.", "/"));
if (MAVEN.GROUP_ID.equals(field)) {
// stop here
return sb.append("/").toString();
}
sb.append("/").append(ai.getArtifactId());
if (MAVEN.ARTIFACT_ID.equals(field)) {
// stop here
return sb.append("/").toString();
}
sb.append("/").append(ai.getVersion());
if (MAVEN.VERSION.equals(field)) {
// stop here
return sb.append("/").toString();
}
sb.append("/").append(ai.getArtifactId()).append("-").append(ai.getVersion());
if (ai.getClassifier() != null) {
sb.append("-").append(ai.getClassifier());
}
sb.append(".").append(ai.getFileExtension() == null ? "jar" : ai.getFileExtension());
return sb.toString();
}
protected Set<String> getGroups(String path, Set<String> allGroups) {
path = path.substring(1).replace('/', '.');
int n = path.length();
Set<String> result = new HashSet<>();
for (String group : allGroups) {
if (group.startsWith(path)) {
group = group.substring(n);
int nextDot = group.indexOf('.');
if (nextDot > -1) {
group = group.substring(0, nextDot);
}
if (group.length() > 0) {
result.add(group);
}
}
}
return result;
}
protected IteratorSearchResponse getArtifacts(TreeNode root, TreeViewRequest request) throws IOException {
if (request.hasFieldHints()) {
return getHintedArtifacts(root, request);
}
String path = root.getPath();
IteratorSearchResponse result;
String g;
String a;
String v;
// "working copy" of path
String wp;
// remove last / from path
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
// 1st try, let's consider path is a group
// reset wp
wp = path;
g = wp.substring(1).replace('/', '.');
result = getArtifactsByG(g, request);
if (result.getTotalHitsCount() > 0) {
return result;
} else {
result.close();
}
// 2nd try, lets consider path a group + artifactId, we must ensure there is at least one / but not as root
if (path.lastIndexOf('/') > 0) {
// reset wp
wp = path;
a = wp.substring(wp.lastIndexOf('/') + 1);
g = wp.substring(1, wp.lastIndexOf('/')).replace('/', '.');
result = getArtifactsByGA(g, a, request);
if (result.getTotalHitsCount() > 0) {
return result;
} else {
result.close();
}
// 3rd try, let's consider path a group + artifactId + version. There is no 100% way to detect this!
try {
// reset wp
wp = path;
v = wp.substring(wp.lastIndexOf('/') + 1);
wp = wp.substring(0, wp.lastIndexOf('/'));
a = wp.substring(wp.lastIndexOf('/') + 1);
g = wp.substring(1, wp.lastIndexOf('/')).replace('/', '.');
result = getArtifactsByGAV(g, a, v, request);
if (result.getTotalHitsCount() > 0) {
return result;
} else {
result.close();
}
} catch (StringIndexOutOfBoundsException e) {
// nothing
}
}
// if we are here, no hits found
return IteratorSearchResponse.empty(result.getQuery());
}
protected IteratorSearchResponse getHintedArtifacts(TreeNode root, TreeViewRequest request) throws IOException {
// we know that hints are there: G hint, GA hint or GAV hint
if (request.hasFieldHint(MAVEN.GROUP_ID, MAVEN.ARTIFACT_ID, MAVEN.VERSION)) {
return getArtifactsByGAV(
request.getFieldHint(MAVEN.GROUP_ID),
request.getFieldHint(MAVEN.ARTIFACT_ID),
request.getFieldHint(MAVEN.VERSION),
request);
} else if (request.hasFieldHint(MAVEN.GROUP_ID, MAVEN.ARTIFACT_ID)) {
return getArtifactsByGA(
request.getFieldHint(MAVEN.GROUP_ID), request.getFieldHint(MAVEN.ARTIFACT_ID), request);
} else if (request.hasFieldHint(MAVEN.GROUP_ID)) {
return getArtifactsByG(request.getFieldHint(MAVEN.GROUP_ID), request);
} else {
// if we are here, no hits found or something horribly went wrong?
return IteratorSearchResponse.empty(null);
}
}
protected IteratorSearchResponse getArtifactsByG(String g, TreeViewRequest request) throws IOException {
return getArtifactsByGAVField(g, null, null, request);
}
protected IteratorSearchResponse getArtifactsByGA(String g, String a, TreeViewRequest request) throws IOException {
return getArtifactsByGAVField(g, a, null, request);
}
protected IteratorSearchResponse getArtifactsByGAV(String g, String a, String v, TreeViewRequest request)
throws IOException {
return getArtifactsByGAVField(g, a, v, request);
}
protected IteratorSearchResponse getArtifactsByGAVField(String g, String a, String v, TreeViewRequest request)
throws IOException {
assert g != null;
Query groupIdQ;
Query artifactIdQ = null;
Query versionQ = null;
// minimum must have
groupIdQ = getIndexer().constructQuery(MAVEN.GROUP_ID, new SourcedSearchExpression(g));
if (StringUtils.isNotBlank(a)) {
artifactIdQ = getIndexer().constructQuery(MAVEN.ARTIFACT_ID, new SourcedSearchExpression(a));
}
if (StringUtils.isNotBlank(v)) {
versionQ = getIndexer().constructQuery(MAVEN.VERSION, new SourcedSearchExpression(v));
}
BooleanQuery.Builder qb = new BooleanQuery.Builder().add(new BooleanClause(groupIdQ, BooleanClause.Occur.MUST));
if (artifactIdQ != null) {
qb.add(new BooleanClause(artifactIdQ, BooleanClause.Occur.MUST));
}
if (versionQ != null) {
qb.add(new BooleanClause(versionQ, BooleanClause.Occur.MUST));
}
IteratorSearchRequest searchRequest = new IteratorSearchRequest(qb.build(), request.getArtifactInfoFilter());
searchRequest.getContexts().add(request.getIndexingContext());
IteratorSearchResponse result = getIndexer().searchIterator(searchRequest);
return result;
}
}