Content by Label - label attribute logic

Charles Reinecke
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
June 14, 2011

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
Atlassian Team members are employees working across the company in a wide variety of roles.
June 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.

1 vote
Kitt Hirasaki December 5, 2011

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
Atlassian Team members are employees working across the company in a wide variety of roles.
December 5, 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?

0 votes
Joerg Bencke
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 11, 2012

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

0 votes
Kitt Hirasaki December 5, 2011

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

0 votes
Kitt Hirasaki July 28, 2011

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

Andreas van Rienen December 5, 2011

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 ...

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events