膜 Rico 和 voidfyoo. orz
定位到如下位置 plugins/nexus-coreui-plugin/src/main/java/org/sonatype/nexus/coreui/ComponentComponent.groovy:185
@Named
@Singleton
@DirectAction(action = 'coreui_Component')
class ComponentComponent
extends DirectComponentSupport
{
...
@DirectMethod
@Timed
@ExceptionMetered
PagedResponse<AssetXO> previewAssets(final StoreLoadParameters parameters) {
String repositoryName = parameters.getFilter('repositoryName')
String expression = parameters.getFilter('expression')
String type = parameters.getFilter('type')
// 接收三个参数 repositoryName 、 expression 、 type
if (!expression || !type || !repositoryName) {
return null
}
// 设置 repositoryName
RepositorySelector repositorySelector = RepositorySelector.fromSelector(repositoryName)
// 根据 type 分别调用不同的 validate
if (type == JexlSelector.TYPE) {
jexlExpressionValidator.validate(expression)
}
else if (type == CselSelector.TYPE) {
cselExpressionValidator.validate(expression)
}
List<Repository> selectedRepositories = getPreviewRepositories(repositorySelector)
if (!selectedRepositories.size()) {
return null
}
def result = browseService.previewAssets(
repositorySelector,
selectedRepositories,
expression,
toQueryOptions(parameters))
return new PagedResponse<AssetXO>(
result.total,
result.results.collect(ASSET_CONVERTER.rcurry(null, null, [:], 0)) // buckets not needed for asset preview screen
)
}
...
}
Nexus为了查询方便,特地在jexl的基础上引入了csel表达式。简单起见,这里不做展开。接着我们跟入browseService.previewAssets
,接口定义在 components/nexus-repository/src/main/java/org/sonatype/nexus/repository/browse/BrowseService.java:59
/**
* Returns a {@link BrowseResult} for previewing the specified repository based on an arbitrary content selector.
*/
BrowseResult<Asset> previewAssets(final RepositorySelector selectedRepository,
final List<Repository> repositories,
final String jexlExpression,
final QueryOptions queryOptions);
具体实现在 components/nexus-repository/src/main/java/org/sonatype/nexus/repository/browse/internal/BrowseServiceImpl.java:233
@Named
@Singleton
public class BrowseServiceImpl
extends ComponentSupport
implements BrowseService
{
...
@Override
public BrowseResult<Asset> previewAssets(final RepositorySelector repositorySelector,
final List<Repository> repositories,
final String jexlExpression,
final QueryOptions queryOptions)
{
checkNotNull(repositories);
checkNotNull(jexlExpression);
final Repository repository = repositories.get(0);
try (StorageTx storageTx = repository.facet(StorageFacet.class).txSupplier().get()) {
storageTx.begin();
List<Repository> previewRepositories;
if (repositories.size() == 1 && groupType.equals(repository.getType())) {
previewRepositories = repository.facet(GroupFacet.class).leafMembers();
}
else {
previewRepositories = repositories;
}
PreviewAssetsSqlBuilder builder = new PreviewAssetsSqlBuilder(
repositorySelector,
jexlExpression,
queryOptions,
getRepoToContainedGroupMap(repositories));
String whereClause = String.format("and (%s)", builder.buildWhereClause());
//The whereClause is passed in as the querySuffix so that contentExpression will run after repository filtering
return new BrowseResult<>(
storageTx.countAssets(null, builder.buildSqlParams(), previewRepositories, whereClause),
Lists.newArrayList(storageTx.findAssets(null, builder.buildSqlParams(),
previewRepositories, whereClause + builder.buildQuerySuffix()))
);
}
}
...
}
注意上面代码中的英文注释,大意为whereClause
条件在完成repository filtering
后将会进行contentExpression
。而whereClause
是通过前面一系列Builder构建的。可以跟入builder.buildWhereClause()
,在 components/nexus-repository/src/main/java/org/sonatype/nexus/repository/browse/internal/PreviewAssetsSqlBuilder.java:51 , 这里最终引入了contentExpression和jexlExpression:
public class PreviewAssetsSqlBuilder
{
...
public String buildWhereClause() {
return whereClause("contentExpression(@this, :jexlExpression, :repositorySelector, " +
":repoToContainedGroupMap) == true", queryOptions.getFilter() != null);
}
...
}
接下来即考虑如何进一步执行contentExpression
。在 components/nexus-repository/src/main/java/org/sonatype/nexus/repository/selector/internal/ContentExpressionFunction.java 。当contentExpression
执行时,会调用execute
方法:
public class ContentExpressionFunction
extends OSQLFunctionAbstract
{
public static final String NAME = "contentExpression";
...
@Inject
public ContentExpressionFunction(final VariableResolverAdapterManager variableResolverAdapterManager,
final SelectorManager selectorManager,
final ContentAuthHelper contentAuthHelper)
{
super(NAME, 4, 4);
this.variableResolverAdapterManager = checkNotNull(variableResolverAdapterManager);
this.selectorManager = checkNotNull(selectorManager);
this.contentAuthHelper = checkNotNull(contentAuthHelper);
}
@Override
public Object execute(final Object iThis,
final OIdentifiable iCurrentRecord,
final Object iCurrentResult,
final Object[] iParams,
final OCommandContext iContext)
{
OIdentifiable identifiable = (OIdentifiable) iParams[0];
// asset
ODocument asset = identifiable.getRecord();
RepositorySelector repositorySelector = RepositorySelector.fromSelector((String) iParams[2]);
// jexlExpression 即 iParams[1]
String jexlExpression = (String) iParams[1];
List<String> membersForAuth;
...
return contentAuthHelper.checkAssetPermissions(asset, membersForAuth.toArray(new String[membersForAuth.size()])) &&
checkJexlExpression(asset, jexlExpression, asset.field(AssetEntityAdapter.P_FORMAT, String.class));
}
其中的iParams
即可对应传入的参数。iParams[0]
即@this
, iParams[1]
即jexlExpression
, iParams[2]
即repositorySelector
。在完成初步筛选出asset
后进入最后的checkJexlExpression
...
private boolean checkJexlExpression(final ODocument asset,
final String jexlExpression,
final String format)
{
VariableResolverAdapter variableResolverAdapter = variableResolverAdapterManager.get(format);
// variableSource 从 asset 中来
VariableSource variableSource = variableResolverAdapter.fromDocument(asset);
SelectorConfiguration selectorConfiguration = new SelectorConfiguration();
selectorConfiguration.setAttributes(ImmutableMap.of("expression", jexlExpression));
// JexlSelector.TYPE 是常量 定义为 'jexl'
selectorConfiguration.setType(JexlSelector.TYPE);
selectorConfiguration.setName("preview");
try {
// 解析表达式
return selectorManager.evaluate(selectorConfiguration, variableSource);
}
catch (SelectorEvaluationException e) {
log.debug("Unable to evaluate expression {}.", jexlExpression, e);
return false;
}
}
}
selectorConfiguration
保存要生成的表达式config。jexlExpression
即前面传入的参数。跟入selectorManager.evaluate
,在 components/nexus-core/src/main/java/org/sonatype/nexus/internal/selector/SelectorManagerImpl.java:156 ,最终执行了表达式
@Override
@Guarded(by = STARTED)
public boolean evaluate(final SelectorConfiguration selectorConfiguration, final VariableSource variableSource)
throws SelectorEvaluationException
{
// 根据传入的 selectorConfiguration 生成对应的 selector
// 前面指定了 JexlSelector.TYPE ,这里将生成 JexlSelector
Selector selector = createSelector(selectorConfiguration);
try {
// 调用 selector 的 evaluate 方法
return selector.evaluate(variableSource);
}
catch (Exception e) {
throw new SelectorEvaluationException("Selector '" + selectorConfiguration.getName() + "' evaluation in error",
e);
}
}
其对应接口位置如下图
如果是新搭建的环境,为复现成功,还需要先往现有的Repository添加asset。这样在查询确实存在asset后,才会进一步根据whereClause
对查询结果asset进行筛选,也才会对whereClause
进行表达式解析。不过在实际环境中,Repository中早就各种asset了。下面随便选了一个logging.jar上传。
POC如下:
增加了权限要求@RequiresPermissions('nexus:selectors:*')