调整HTML视图下面排序

业务需求:

我们用nexus 主要存放android 编译的版本,版本子目录 安装 日期 来命名的

  • 20230318_060159_nexus_dev_3.43.0
  • 20230320_101553_nexus_dev_3.43.0
  • 20230321_060159_nexus_dev_3.43.0

如上所示,在nexus中这个HTML视图页面下面排序是从上往下日期是越来越新的,这样就会有个问题,业务方每天去取最新的版本就要 把页面滚动到 最下面才行。

能不能调整这个排序规则呢??

分析整个调整排序的思路

第一步

通过 右键 浏览器 -> 检查 来查看页面源码, 查看有什么关键,比较 特殊的标识,标签名,属性名 ,

然后用这个 关键字符串 在 整个源码中去搜索,先定位到 这个页面是在源码的哪个文件里面的。

我们这里 使用 “Parent Directory” 这个关键字符串去搜索。

在 文件 components/nexus-repository-services/src/main/resources/org/sonatype/nexus/repository/rest/internal/resources/browseContentHtml.vm 找到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<table cellspacing="10">
<tr>
<th align="left">Name</th>
<th>Last Modified</th>
<th>Size</th>
<th>Description</th>
</tr>
#if(! $root)
<tr>
<td><a href="../">Parent Directory</a></td>
</tr>
#end
#foreach( $listItem in $listItems )
<tr>
## resourceUri is escaped in RepositoryBrowseResource.getListItemPath
<td><a href="$listItem.resourceUri">$esc.html($listItem.name)</a></td>
<td>
#if( $listItem.collection )
&nbsp;
#else
$listItem.lastModified
#end
</td>
<td align="right">
#if( $listItem.collection )
&nbsp;
#else
$listItem.size
#end
</td>
<td>$listItem.description</td>
</tr>
#end
</table>

通过分析知道,这个表格 是 循环 #foreach( $listItem in $listItems ) 来渲染出来的。

第二步

然后就是 需要知道 listItems 这个东西是来自哪里,哪里传过来的???

可以尝试 用这个 关键字 去 components/nexus-repository-services 搜索,没搜到 再扩大范围。

这里应该能想到 这个 listItems 估计是 java 代码中 赋值,然后到 这种 vm 模板文件中渲染的。 所以 可以尝试 只搜索 java代码。

在 browseContentHtml.vm 文件中 也还有其他 关键的地方 可以用来 搜索

50 行的 #if($showMoreContentWarning) 这个 估计也是 java 中定义的一个方法。 这个模板渲染和其他框架 都是很类似的。

第三步

通过 查找,搜索 定位到 java 文件.

通过源码我们知道几件事情

  • 这里采用的 是 VelocityEngine 模板引擎
  • showMoreContentWarning 不是一个java中的方法,而是 渲染模板时候 传入的 一个变量参数。
  • 同样的模板参数有 root, requestPath, listItems。。。。在 TemplateHelperImpl类中也有模板参数的设置

components/nexus-repository-services/src/main/java/org/sonatype/nexus/repository/rest/internal/resources/RepositoryBrowseResource.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2008-present Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.repository.rest.internal.resources;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import org.sonatype.goodies.common.ComponentSupport;
import org.sonatype.nexus.common.template.TemplateHelper;
import org.sonatype.nexus.common.template.TemplateParameters;
import org.sonatype.nexus.common.text.Strings2;
import org.sonatype.nexus.repository.Repository;
import org.sonatype.nexus.repository.browse.node.BrowseListItem;
import org.sonatype.nexus.repository.browse.node.BrowseNode;
import org.sonatype.nexus.repository.browse.node.BrowseNodeConfiguration;
import org.sonatype.nexus.repository.browse.node.BrowseNodeQueryService;
import org.sonatype.nexus.repository.manager.RepositoryManager;
import org.sonatype.nexus.repository.security.RepositoryViewPermission;
import org.sonatype.nexus.rest.Resource;
import org.sonatype.nexus.security.SecurityHelper;

import com.google.common.collect.Iterables;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Arrays.asList;
import static javax.ws.rs.core.MediaType.TEXT_HTML;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static org.sonatype.nexus.common.encoding.EncodingUtil.urlEncode;
import static org.sonatype.nexus.security.BreadActions.BROWSE;

/**
* @since 3.6
*/
@Named
@Singleton
@Path(RepositoryBrowseResource.RESOURCE_URI)
@Produces(TEXT_HTML)
public class RepositoryBrowseResource
extends ComponentSupport
implements Resource
{
private static final String REPOSITORY_PATH_SEGMENT = "{noop: (/)?}{repositoryPath: ((?<=/).*)?}";

public static final String RESOURCE_URI = "/repository/browse/{repositoryName}" + REPOSITORY_PATH_SEGMENT;

private static final String TEMPLATE_RESOURCE = "browseContentHtml.vm";

private final RepositoryManager repositoryManager;

private final BrowseNodeQueryService browseNodeQueryService;

private final BrowseNodeConfiguration configuration;

private final TemplateHelper templateHelper;

private final SecurityHelper securityHelper;

private final URL template;

@Inject
public RepositoryBrowseResource(
final RepositoryManager repositoryManager,
final BrowseNodeQueryService browseNodeQueryService,
final BrowseNodeConfiguration configuration,
final TemplateHelper templateHelper,
final SecurityHelper securityHelper)
{
this.repositoryManager = checkNotNull(repositoryManager);
this.browseNodeQueryService = checkNotNull(browseNodeQueryService);
this.configuration = checkNotNull(configuration);
this.templateHelper = checkNotNull(templateHelper);
this.securityHelper = checkNotNull(securityHelper);
this.template = getClass().getResource(TEMPLATE_RESOURCE);
checkNotNull(template);
}

@GET
public Response getHtml(@PathParam("repositoryName") final String repositoryName,
@PathParam("repositoryPath") final String repositoryPath,
@Context final UriInfo uriInfo)
{
log.debug("Get HTML directory listing for repository {} on path {}", repositoryName, repositoryPath);

if (!uriInfo.getAbsolutePath().toString().endsWith("/")) {
log.debug("Request does include a trailing slash, redirecting to include it");
return Response.seeOther(UriBuilder.fromUri(uriInfo.getAbsolutePath()).path("/").build()).build();
}

Repository repository = repositoryManager.get(repositoryName);

if (repository == null) {
throw createNotFoundException(repositoryName, null);
}

List<String> pathSegments = new ArrayList<>();

if (!isRoot(repositoryPath)) {
pathSegments = asList(repositoryPath.split("/"));
}

Iterable<BrowseNode> browseNodes =
browseNodeQueryService.getByPath(repository, pathSegments, configuration.getMaxHtmlNodes());

final boolean permitted = securityHelper.allPermitted(new RepositoryViewPermission(repository, BROWSE));
final boolean hasChildren = browseNodes != null && !Iterables.isEmpty(browseNodes);
final List<BrowseListItem> listItems = hasChildren ?
browseNodeQueryService.toListItems(repository, browseNodes) :
Collections.emptyList();

//if there are visible children return them, or if we are at the root node and permitted to browse the repo
if (hasChildren || (isRoot(repositoryPath) && permitted)) {
return Response
.ok(templateHelper.render(template, initializeTemplateParameters(repositoryName, repositoryPath, listItems)))
.build();
}

throw createNotFoundException(repositoryName, permitted ? repositoryPath : null);
}

private WebApplicationException createNotFoundException(final String repositoryName, final String repositoryPath) {
if (repositoryPath == null) {
log.debug("Requested repository could not be located or user does not have permission: {} ", repositoryName);
return new WebApplicationException("Repository not found", NOT_FOUND);
}
else {
log.debug("Requested path {} could not be located in repository {}", repositoryPath, repositoryName);
return new WebApplicationException("Path not found", NOT_FOUND);
}
}

private TemplateParameters initializeTemplateParameters(final String repositoryName, final String path, final List<BrowseListItem> listItems) {
TemplateParameters templateParameters = templateHelper.parameters();

if (isRoot(path)) {
templateParameters.set("root", true);
}

templateParameters.set("requestPath", "/" + path);
templateParameters.set("listItems", listItems);

templateParameters.set("showMoreContentWarning", configuration.getMaxHtmlNodes() == listItems.size());
if (Strings2.isBlank(path)) {
templateParameters.set("encodedPath", String.format("/#browse/browse:%s", repositoryName));
}
else {
String encodedPath = urlEncode("/" + path + "/");
templateParameters.set("encodedPath", String.format("/#browse/browse:%s:%s", repositoryName, encodedPath));
}
templateParameters.set("searchUrl", "/#browse/search");

return templateParameters;
}

private boolean isRoot(final String path) {
return Strings2.isBlank(path);
}
}


getHtml 方法

这个就是我们最终定位需要修改的方法

listItems 是一个 List<BrowseListItem> 我们直接对其进行排序 就行。

最简单的改法 就是 加个 Collections.reverse(listItems);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

@GET
public Response getHtml(@PathParam("repositoryName") final String repositoryName,
@PathParam("repositoryPath") final String repositoryPath,
@Context final UriInfo uriInfo)
{
log.debug("Get HTML directory listing for repository {} on path {}", repositoryName, repositoryPath);

if (!uriInfo.getAbsolutePath().toString().endsWith("/")) {
log.debug("Request does include a trailing slash, redirecting to include it");
return Response.seeOther(UriBuilder.fromUri(uriInfo.getAbsolutePath()).path("/").build()).build();
}

Repository repository = repositoryManager.get(repositoryName);

if (repository == null) {
throw createNotFoundException(repositoryName, null);
}

List<String> pathSegments = new ArrayList<>();

if (!isRoot(repositoryPath)) {
pathSegments = asList(repositoryPath.split("/"));
}

Iterable<BrowseNode> browseNodes =
browseNodeQueryService.getByPath(repository, pathSegments, configuration.getMaxHtmlNodes());

final boolean permitted = securityHelper.allPermitted(new RepositoryViewPermission(repository, BROWSE));
final boolean hasChildren = browseNodes != null && !Iterables.isEmpty(browseNodes);
final List<BrowseListItem> listItems = hasChildren ?
browseNodeQueryService.toListItems(repository, browseNodes) :
Collections.emptyList();
Collections.reverse(listItems);
//if there are visible children return them, or if we are at the root node and permitted to browse the repo
if (hasChildren || (isRoot(repositoryPath) && permitted)) {
return Response
.ok(templateHelper.render(template, initializeTemplateParameters(repositoryName, repositoryPath, listItems)))
.build();
}

throw createNotFoundException(repositoryName, permitted ? repositoryPath : null);
}