| /* |
| * 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; |
| } |
| } |