源码阅读
我们先读一下插件源码(Github)
层级结构
nexus插件本质上也是个maven项目,最终打成Kar包给nexus使用
dto包
AssociatedComponent: 封装接口调用时传过来的制品属性,可以看到成员变量里面group不可以为null,对于pypi和docker制品(group只能是null),无法打标签。而作者这么定义group也是受nexus内部api限制。
@ComponentExists
public class AssociatedComponent {
@NotNull(message = "Components repository can't be null.")
@NotBlank
private String repository;
@NotNull
private String group;
@NotNull(message = "Components name can't be null.")
@NotBlank
private String name;
private String version;
}
Tag:tag标签的pojo对象,我们调用tag的查询接口时,tag封装成该对象。
TagDefinition:tag的定义,我们调用tag的新增接口时,传参会封装成该对象。
exception包
底下各类顾名思义,对应出现各种错误时throw的异常
根目录
重点就说一个类TagRestResource,类似于SpringMVC的Controller,定义各种接口的
@Named
@Singleton
@Path(V1_API_PREFIX + "/tags")
public class TagRestResource extends ComponentSupport implements Resource, TagRestResourceDoc {
private final TagStore tagStore;
private final Validator validator;
@Inject
public TagRestResource(TagStore tagStore, Validator validator) {
this.tagStore = tagStore;
this.validator = validator;
}
@GET
@Path("/{name}")
@Produces(MediaType.APPLICATION_JSON)
@Override
public Tag getByName(@PathParam("name") String name) {
return tagStore.getByName(name);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Override
public List<Tag> list(@QueryParam("attributes") String attributes) {
Map<String, String> attributeMap = decodeAttributes(attributes);
List<Tag> tags = tagStore.search(attributeMap);
log.info("Tag search for attributes={}={}", attributes, tags);
return tags;
}
/**
* 新增tag
*/
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Override
public Tag add(TagDefinition definition) {
//新增tag时,会验证传过来的制品属性能不能匹配到制品
validate(definition);
// 储存tag
return tagStore.addOrUpdate(definition);
}
....
private void validate(Object object) {
// 调用nexus内部逻辑匹配制品
Set<ConstraintViolation<Object>> violations = validator.validate(object);
if (!violations.isEmpty()) {
throw new ConstraintViolationException("Invalid tag.", violations);
}
}
}
validation包
TagRestResource类的validate方法调用nexus内部逻辑匹配制品,最后会调用ComponentExistsValidator类
@Named
@Singleton
public class ComponentExistsValidator extends ConstraintValidatorSupport<ComponentExists, AssociatedComponent> {
private final RepositoryManager repositoryManager;
private final ComponentStore componentStore;
@Inject
public ComponentExistsValidator(RepositoryManager repositoryManager, ComponentStore componentStore) {
this.repositoryManager = repositoryManager;
this.componentStore = componentStore;
}
// 开始匹配制品
@Override
public boolean isValid(AssociatedComponent value, ConstraintValidatorContext context) {
// 获取tag传过来的仓库
Repository repository = repositoryManager.get(value.getRepository());
// 仓库是否存在??
if (repository == null) {
log.info("Component {} is invalid as repository does not exists.", value);
return false;
}
// 封装tag传过来的制品的各种属性
Map<String, String> versionAttribute = new HashMap<>();
if (value.getVersion() != null && !value.getVersion().isEmpty()) {
versionAttribute.put("version", value.getVersion());
}
// 此处调用nexus内部逻辑,用封装的属性寻找制品
List<Component> founds = componentStore.getAllMatchingComponents(repository, value.getGroup(), value.getName(),
versionAttribute);
log.info("Components found for {}: {}", value, founds);
return !founds.isEmpty();
}
}
那我们就找一下getAllMatchingComponents这个方法做了什么?
// 该方法在org.sonatype.nexus.repository.storage下
public List<Component> getAllMatchingComponents(Repository repository, String group, String name, Map<String, String> formatAttributes) {
// nexus在寻找匹配的制品时,要求各种属性不可以为null,才继续匹配
// 这就是为什么作者写插件时候AssociatedComponent的group设置@NotNull
Preconditions.checkNotNull(repository);
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(name);
Preconditions.checkNotNull(formatAttributes);
Throwable var6 = null;
Object var7 = null;
....
}
至此,我们知道了,这个插件缺陷蛮大的,兼容性不好。作者自己也在源码中的TODO说目前只支持maven制品库
源码修改
首先明确一下思路,我们由于匹配时group非null,导致插件无法使用。那么我们可以转变思路,我们可以利用nexus的searchAPI进行制品的查找(nexus有很多开发接口供调用)。所以我们小小修改一下TagRestResource类,也就是修改一下validate方法,让该方法不去调nexus的api,而是调searchAPI,该API返回json我们转换成pojo,并判断pojo是否为null且唯一来确认制品的匹配
当然,源码里面还有其他的修改,都是比较简单的逻辑,这里放上源码
public class TagRestResource extends ComponentSupport implements Resource, TagRestResourceDoc {
private final TagStore tagStore;
private final Validator validator;
private final String NexusProtocol = "http://";
private final String NexusHost = "xxx.xxx.xxx.xxx:xxx";
private final String NexusSearchPath = "/service/rest/v1/search?";
private final static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
......
/**
* 检验流程:
* 获取制品信息(POST请求体) -->
* 调用nexusAPI搜索匹配的制品 -->
* 判断是否唯一匹配
*/
private void validate(Object object) {
if (object instanceof TagDefinition) {
TagDefinition tagDefinition = (TagDefinition) object;
String uri = null;
WebTarget target = null;
for (AssociatedComponent component : tagDefinition.getComponents()) {
String name = component.getName();
String group = component.getGroup();
String repository = component.getRepository();
String version = component.getVersion();
searchMatchedComponent(repository, name, version, group);
}
} else if (object instanceof TagCloneRequest) {
// 不涉及制品匹配,用原有逻辑
Set<ConstraintViolation<Object>> violations = validator.validate(object);
if (!violations.isEmpty()) {
throw new ConstraintViolationException("Invalid tag.", violations);
}
}
}
/**
* 为什么用原生JDK发起http请求?
* 因为插件依赖的模块在安装插件后都会成为一个bundle,就比如本插件依赖了Jackson-core、Jackson-annotations、jackson-databind
* 我们安装插件后实际上新增了4个bundle,也就是三面三个模块加tag插件本身
* 所以我选择尽量避免依赖太多额外的模块,避免可能的冲突
*/
private void searchMatchedComponent(String repository, String name, String version, String group) {
try {
String searchUrl = NexusProtocol + NexusHost + NexusSearchPath;
if (group != null) {
searchUrl += "name=" + name + "&group=" + group + "&version=" + version + "&repository=" + repository;
} else {
searchUrl += "name=" + name + "&version=" + version + "&repository=" + repository;
}
URL url = new URL(searchUrl);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestMethod("GET");
InputStreamReader inputStreamReader = new InputStreamReader(httpURLConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String json = "";
while (true) {
String readLine = bufferedReader.readLine();
if (readLine == null) {
break;
}
json += readLine;
}
bufferedReader.close();
try {
SearchResult searchResult = objectMapper.readValue(json, SearchResult.class);
// 当且仅当存在唯一对应的制品则检验通过
if (searchResult == null || searchResult.getItems().size() != 1) {
throw new ServerException("Cannot match the component by json :" + json);
}
} catch (JsonProcessingException e) {
throw new RuntimeException("json data erro:" + json);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
打包测试
参考《为nexus安装插件:nexus-tag-plugin为例》这篇博客,安装并测试,成功(截图略)
评论区