Content by Label - label attribute logic

When using the "label" attribute as follows

label=AA,BB,+CC

It appears to behave as follows.

  • must match CC
    AND
  • can match AA, BB or neither

"Neither" is not really what I wanted. I want (AA or BB) AND CC

5 answers

2 votes
David Chan Atlassian Team Jun 21, 2011

The only way I've found to do this is to actually split the macro up into 2 macros. Although it's a bit more work, it should display as normal. For example, to achieve what you want do:

label=+AA,+CC

label=+BB,+CC,-AA

The reason for the -AA is to prevent duplicate labels.

This is a little out of context, but hopefully these two files will give you a pretty good idea of what I did.

First file, "ORLabelParameter.java":

package com.pixar.confluence.plugins;

import com.atlassian.confluence.macro.query.SearchQueryInterpreter;

import com.atlassian.confluence.macro.query.SearchQueryInterpreterException;

import com.atlassian.confluence.macro.query.params.BooleanQueryFactoryParameter;

import com.atlassian.confluence.macro.MacroExecutionContext;

import com.atlassian.confluence.search.v2.SearchQuery;

import com.atlassian.confluence.search.v2.query.LabelQuery;

import com.atlassian.confluence.labels.LabelManager;

import com.atlassian.confluence.labels.LabelParser;

import com.atlassian.spring.container.ContainerManager;

/**

* Presents label parameters as {@link com.atlassian.confluence.macro.query.BooleanQueryFactory} implementations.

*

* @since 2.9

*/

public class ORLabelParameter extends BooleanQueryFactoryParameter

{

private static final String[] DEFAULT_PARAM_NAMES = { "orlabel", "orlabels" };

private LabelManager labelManager;

public ORLabelParameter()

{

this(null);

}

public ORLabelParameter(String defaultValue)

{

super(DEFAULT_PARAM_NAMES, defaultValue);

ContainerManager.autowireComponent(this);

}

static class Interpreter implements SearchQueryInterpreter

{

private boolean shouldValidate;

private LabelManager labelManager;

public void setShouldValidate(boolean shouldValidate)

{

this.shouldValidate = shouldValidate;

}

public void setLabelManager(LabelManager labelManager)

{

this.labelManager = labelManager;

}

public SearchQuery createSearchQuery(String value) throws SearchQueryInterpreterException

{

if (shouldValidate)

{

if (labelManager.getLabel(value) == null)

{

throw new SearchQueryInterpreterException("'" + value + "' is not an existing label");

}

}

/**

* CONF-15398. We want to report invalid labels even if validation is suppressed using shouldValidate = false,

* as you cannot make a valid search query out of an invalid label.

*

* Ideally this validation should performed on the value passed to createSearchQuery() before it is invoked.

*/

if (LabelParser.parse(value) == null)

throw new SearchQueryInterpreterException("'" + value + "' is an invalid label.");

return new LabelQuery(value);

}

}

@Override

protected SearchQueryInterpreter createSearchQueryInterpreter(MacroExecutionContext ctx)

{

Interpreter interpreter = new Interpreter();

interpreter.setShouldValidate(shouldValidate);

interpreter.setLabelManager(labelManager);

return interpreter;

}

public void setLabelManager(LabelManager labelManager)

{

this.labelManager = labelManager;

}

}

Second file, LabelledContentMacro.java:
package com.pixar.confluence.plugins;
import com.atlassian.bonnie.Searchable;
import com.atlassian.confluence.core.ConfluenceActionSupport;
import com.atlassian.confluence.core.SpaceContentEntityObject;
import com.atlassian.confluence.macro.ContentFilteringMacro;
import com.atlassian.confluence.macro.MacroExecutionContext;
import com.atlassian.confluence.macro.params.ParameterException;
import com.atlassian.confluence.macro.query.BooleanQueryFactory;
import com.atlassian.confluence.macro.query.InclusionCriteria;
import com.atlassian.confluence.renderer.radeox.macros.MacroUtils;
import com.atlassian.confluence.search.v2.*;
import com.atlassian.confluence.search.v2.filter.SubsetResultFilter;
import com.atlassian.confluence.search.v2.query.BooleanQuery;
import com.atlassian.confluence.search.v2.query.LabelQuery;
import com.atlassian.confluence.search.v2.query.CreatorQuery;
import com.atlassian.confluence.search.v2.query.InSpaceQuery;
import com.atlassian.confluence.search.v2.searchfilter.ContentPermissionsSearchFilter;
import com.atlassian.confluence.search.v2.searchfilter.SpacePermissionsSearchFilter;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.confluence.util.ContentMacrosHelper;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.confluence.util.velocity.VelocityUtils;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.macro.MacroException;
import com.atlassian.spring.container.ContainerManager;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.atlassian.confluence.pages.AbstractPage;
import com.atlassian.confluence.pages.DefaultPageManager;
import com.atlassian.confluence.pages.PageManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.Collections;
public class LabelledContentMacro extends ContentFilteringMacro
{
private static final Logger log = Logger.getLogger(LabelledContentMacro.class);
private static final String TEMPLATE_NAME = "com/pixar/confluence/plugins/docfilters/labelledcontent.vm";
private static final String SPACE_KEY_BC_PARAM = "key";
private static final String SPACE_CURRENT = "@self";
private static final String SPACE_ALL = "@all";
private static final String OPERATOR = "operator";
private static final String OPERATOR_AND = "and";
private static final String DEPT_PREFIX = "dept=";
private static final String DEPT_ALL = DEPT_PREFIX + "all";
private static final String SHOW_PREFIX = "show=";
private static final String SHOW_ALL = SHOW_PREFIX + "all";
private ContentMacrosHelper contentMacrosHelper;
private ConfluenceActionSupport confluenceActionSupport;
protected final ORLabelParameter orlabelParam;
public class DocTableOutput
{
public int type;
public Searchable content;
public String spaceKey;
public int getType() {
return type;
}
public Searchable getContent() {
return content;
}
public String getSpaceKey() {
return spaceKey;
}
}
public LabelledContentMacro()
{
super();
labelParam.setValidate(false); // CONF-14235 - this macro shouldn't try to validate labels
orlabelParam = new ORLabelParameter();
}
public boolean isInline()
{
return false;
}
public boolean hasBody()
{
return false;
}
public RenderMode getBodyRenderMode()
{
return RenderMode.NO_RENDER;
}
@Override
public String execute(MacroExecutionContext ctx) throws MacroException
{
return executeSearch(ctx);
}
public String executeSearch(MacroExecutionContext ctx) throws MacroException
{
Map<String, Object> contextMap = getMacroVelocityContext();
Map<String, String> parameters = ctx.getParams();
String title = parameters.get("title");
BooleanQueryFactory queryFactory = new BooleanQueryFactory();
// CONF-12749 Limit the label links to a single space if we only search a single space
boolean limitLabelLinksToSpace = false;
// parse the space(s) parameter
spaceKeyParam.addParameterAlias("key");
// CONF-14023 - default to searching all spaces
spaceKeyParam.setDefaultValue(SPACE_ALL);
BooleanQueryFactory spaceKeyQuery = spaceKeyParam.findValue(ctx);
if (spaceKeyQuery != null)
{
queryFactory.addMust(spaceKeyQuery.toBooleanQuery());
limitLabelLinksToSpace = isSingleSpace(spaceKeyQuery);
}
// use the "labels" parameter from ContentFilteringMacro
labelParam.addParameterAlias("0");
if (parameters.containsKey(OPERATOR) && parameters.get(OPERATOR).equalsIgnoreCase(OPERATOR_AND))
{
labelParam.setDefaultInclusionCriteria(InclusionCriteria.ALL);
}
else
{
labelParam.setDefaultInclusionCriteria(InclusionCriteria.ANY);
}
BooleanQueryFactory labelQuery = labelParam.findValue(ctx);
if (labelQuery != null)
{
queryFactory.addMust(labelQuery.toBooleanQuery());
}
else
{
throw new MacroException(
getConfluenceActionSupport().getText("contentbylabel.error.label-parameter-required")
);
}
List<String> labelNames = new ArrayList<String>();
// we need a list of label names to display in case no content comes back.
// For now, we're going to just stick the label string into a single-element list.
labelNames.add(labelParam.getParameterValue(parameters));
contextMap.put("labelNames", labelNames);
String orlabelString = orlabelParam.getParameterValue(parameters);
if (orlabelString != null && !orlabelString.isEmpty()) {
String[] orlabelNames = StringUtils.split(orlabelString, ',');
if (orlabelNames.length > 0) {
Vector<LabelQuery> orqueries = new Vector<LabelQuery>();
for (String orlName : orlabelNames) {
orqueries.add(new LabelQuery(orlName));
}
SearchQuery orQuery;
if (orqueries.size() > 1) {
orQuery = orqueries.remove(0);
while (orqueries.size() > 0) {
orQuery = BooleanQuery.composeOrQuery(orQuery, orqueries.remove(0));
}
}
else {
orQuery = orqueries.get(0);
}
queryFactory.addMust(orQuery);
}
}
String myDept = FilterUtils.getDept();
SearchQuery deptQuery;
LabelQuery myDeptQuery;
LabelQuery allDeptQuery = new LabelQuery(DEPT_ALL);
if (myDept != null && myDept != "")
{
myDeptQuery = new LabelQuery(DEPT_PREFIX + myDept);
deptQuery = BooleanQuery.composeOrQuery(myDeptQuery, allDeptQuery);
}
else
{
deptQuery = allDeptQuery;
}
if (deptQuery != null)
{
if (!myDept.equals("all")) {
queryFactory.addMust(deptQuery);
}
}
else
{
throw new MacroException(
getConfluenceActionSupport().getText("contentbylabel.error.no-dept")
);
}
String myShow = FilterUtils.getShow();
SearchQuery showQuery;
LabelQuery myShowQuery;
LabelQuery allShowQuery = new LabelQuery(SHOW_ALL);
if (myShow != null && myShow != "")
{
myShowQuery = new LabelQuery(SHOW_PREFIX + myShow);
showQuery = BooleanQuery.composeOrQuery(myShowQuery, allShowQuery);
}
else
{
showQuery = allShowQuery;
}
if (showQuery != null)
{
if (!myShow.equals("all")) {
queryFactory.addMust(showQuery);
}
}
else
{
throw new MacroException(
getConfluenceActionSupport().getText("contentbylabel.error.no-show")
);
}
// handle the other parameters.
SubsetResultFilter subsetResultFilter;
maxResultsParam.addParameterAlias("maxResults");
maxResultsParam.setDefaultValue(ContentFilteringMacro.DEFAULT_MAX_RESULTS);
try
{
Integer maxResults = maxResultsParam.findValue(ctx);
contextMap.put("maxResults", maxResults);
subsetResultFilter = new SubsetResultFilter(maxResults);
}
catch (ParameterException pe)
{
throw new MacroException(
getConfluenceActionSupport().getText("contentbylabel.error.parse-max-labels-param"),
pe);
}
SearchSort searchSort;
try
{
searchSort = sortParam.findValue(ctx);
}
catch (ParameterException pe)
{
throw new MacroException(
getConfluenceActionSupport().getText("contentbylabel.error.parse-reverse-or-sort-param"),
pe);
}
try
{
BooleanQueryFactory contentTypeQuery = contentTypeParam.findValue(ctx);
if (contentTypeQuery != null)
{
queryFactory.addMust(contentTypeQuery.toBooleanQuery());
}
}
catch (ParameterException pe)
{
throw new MacroException(
getConfluenceActionSupport().getText(
"contentbylabel.error.parse-types-param",
new String[] { StringEscapeUtils.escapeHtml(pe.getMessage()) }
),
pe
);
}
// parse author parameter
Set<String> authors = authorParam.findValue(ctx);
if (!authors.isEmpty())
{
BooleanQueryFactory authorQueryFactory = new BooleanQueryFactory();
for (String author : authors)
{
authorQueryFactory.addShould(new CreatorQuery(author));
}
queryFactory.addMust(authorQueryFactory.toBooleanQuery());
}
BooleanQuery query = queryFactory.toBooleanQuery();
final SearchFilter searchFilter = ContentPermissionsSearchFilter.getInstance().and(SpacePermissionsSearchFilter.getInstance());
ISearch search = new ContentSearch(query, searchSort, searchFilter, subsetResultFilter);
SearchResults searchResults;
try
{
searchResults = searchManager.search(search);
}
catch (InvalidSearchException ise)
{
throw new MacroException(getConfluenceActionSupport().getText("contentbylabel.error.run-search"), ise);
}
List<Searchable> rawcontents = searchManager.convertToEntities(searchResults, true);
// Do not include our enclosing page in the list of results.
long pageId = ctx.getContent().getId();
Vector<DocTableOutput> contents = new Vector<DocTableOutput>();
/*
Vector<DocTableOutput> contentsPixdoc = new Vector<DocTableOutput>();
Vector<DocTableOutput> contentsProd = new Vector<DocTableOutput>();
*/
/*
DocTableOutput headerCell = new DocTableOutput();
headerCell.content = null;
headerCell.spaceKey = "";
headerCell.type = 0;
contentsProd.add(headerCell);
*/
PageManager pageManager = (PageManager) ContainerManager.getComponent("pageManager");
for( Searchable content: rawcontents) {
if (content.getId() != pageId) {
DocTableOutput output = new DocTableOutput();
output.content = content;
String spaceKey = pageManager.getAbstractPage(content.getId()).getSpaceKey();
output.spaceKey = spaceKey;
if (spaceKey.equals("pixdoc")) {
output.type = 2;
//contentsPixdoc.add(output);
} else {
output.type = 1;
//contentsProd.add(output);
}
contents.add(output);
}
}
/*
if(contentsProd.size() > 0) {
contents.addAll(contentsProd);
}
contents.addAll(contentsPixdoc);
*/
// If our results contain more than ten items, split them between two columns.
List<DocTableOutput> contentsA;
List<DocTableOutput> contentsB;
int lenA = (contents.size() / 2) + (contents.size() % 2);
contentsA = contents.subList(0, lenA);
contentsB = contents.subList(lenA, contents.size());
/*
if (contents.size() > 10) {
int lenA = (contents.size() / 2) + (contents.size() % 2);
contentsA = contents.subList(0, lenA);
contentsB = contents.subList(lenA, contents.size());
}
else
{
contentsA = contents;
contentsB = contents.subList(0,0);
contentsB.clear();
}
*/
contextMap.put("title", title);
contextMap.put("contentsA", contentsA);
contextMap.put("contentsB", contentsB);
contextMap.put("unfilteredResultsCnt", searchResults.getUnfilteredResultsCount());
contextMap.put("showLabels", getBooleanParameter(parameters.get("showLabels"), true));
contextMap.put("showSpace", getBooleanParameter(parameters.get("showSpace"), true));
contextMap.put("limitLabelLinksToSpace", limitLabelLinksToSpace);
contextMap.put("contentMacrosHelper", contentMacrosHelper);
contextMap.put("pageContext", ctx.getPageContext());
contextMap.put("showExcerpt", Boolean.valueOf(parameters.get("excerpt")));
contextMap.put("embedTable", getBooleanParameter(parameters.get("embedTable"), false));
return render(contextMap);
}
/* We assume that the query applies to a single space if there's
* only one component and it is an InSpace query type.
*
* TODO This probably belongs in the SpaceQuery class
*/
private boolean isSingleSpace(BooleanQueryFactory spaceKeyQuery)
{
boolean limitLabelLinksToSpace = false;
List params = spaceKeyQuery.toBooleanQuery().getParameters();
if(params.size() == 1 && (params.get(0) instanceof InSpaceQuery) )
{
limitLabelLinksToSpace = true;
}
return limitLabelLinksToSpace;
}
/// CLOVER:OFF
@SuppressWarnings("unchecked")
protected Map<String, Object> getMacroVelocityContext()
{
// current MacroUtils returns untyped map, but its keys are always
// strings and we don't want to restrict what it can hold
return MacroUtils.defaultVelocityContext();
}
protected String render(Map<String, Object> contextMap)
{
return VelocityUtils.getRenderedTemplate(TEMPLATE_NAME, contextMap);
}
///CLOVER:ON
private Boolean getBooleanParameter(String booleanValue, boolean defaultValue)
{
if (StringUtils.isNotBlank(booleanValue))
return Boolean.valueOf(booleanValue);
else
return defaultValue;
}
public void setContentMacrosHelper(ContentMacrosHelper contentMacrosHelper)
{
this.contentMacrosHelper = contentMacrosHelper;
}
protected ConfluenceActionSupport getConfluenceActionSupport()
{
if (null == confluenceActionSupport)
confluenceActionSupport = GeneralUtil.newWiredConfluenceActionSupport();
return confluenceActionSupport;
}
}
Joe Clark Atlassian Team Dec 05, 2011

Thanks so much for sharing your code - I'll see if we can get these enhancements contributed back to Confluence - if you don't have any objections?

I wanted to do the same thing and ended up creating my own hacked-up version of the macro.

Kitt - is there a chance that you share the code of your modified macro. In my opinion more advanced AND/OR "queries" are a useful feature for more advanced requirements ... Thanks for a brief reply ...

Sure, if they're helpful, go for it!

Has anybody extended this to filter "pagetreeonly" vs. "global search" ?

Suggest an answer

Log in or Sign up to answer
Community showcase
Posted Oct 24, 2018 in Confluence

Atlassian Research opportunity with Confluence templates

Do you use templates with Confluence? Take part in a remote 1-hr workshop. You'll receive USD $100 for your time!   We're looking for people to participate in a   remote 1-hr workshop...

1,056 views 16 14
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you