From d82f97fc8059ea49e82961fefc73a2ae536619a2 Mon Sep 17 00:00:00 2001
From: Jean-Noël Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Tue, 29 Mar 2016 10:15:49 +0000
Subject: [PATCH] Prep work for OPENDJ-2803 Migrate Attribute
---
opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java | 132 +++------------------
opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPFilter.java | 129 +++++++++-----------
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java | 10 -
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java | 79 ++----------
4 files changed, 98 insertions(+), 252 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java
index c7ca25e..0a093ed 100644
--- a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java
+++ b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java
@@ -89,6 +89,8 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.ConfigurationFramework;
import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.Syntax;
@@ -117,7 +119,6 @@
import org.opends.server.core.LockFileManager;
import org.opends.server.schema.SchemaConstants;
import org.opends.server.schema.SomeSchemaElement;
-import org.forgerock.opendj.ldap.DN;
import org.opends.server.types.OpenDsException;
import org.opends.server.types.Schema;
import org.opends.server.util.ServerConstants;
@@ -1473,12 +1474,7 @@
*/
public static String getAttributeNameWithoutOptions(String attrName)
{
- int index = attrName.indexOf(";");
- if (index != -1)
- {
- attrName = attrName.substring(0, index);
- }
- return attrName;
+ return AttributeDescription.valueOf(attrName).getNameOrOID();
}
/**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPFilter.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPFilter.java
index ec3dee9..aef820c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPFilter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPFilter.java
@@ -18,13 +18,15 @@
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.StringTokenizer;
+import java.util.Set;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.ResultCode;
@@ -76,8 +78,8 @@
/** The filter component for NOT filters. */
private RawFilter notComponent;
- /** The attribute type for several filter types. */
- private String attributeType;
+ /** The attribute description for several filter types. */
+ private String attributeDescription;
/** The matching rule ID for extensible matching filters. */
private String matchingRuleID;
@@ -94,7 +96,7 @@
* @param filterType The filter type for this filter.
* @param filterComponents The filter components for AND and OR filters.
* @param notComponent The filter component for NOT filters.
- * @param attributeType The attribute type for this filter.
+ * @param attributeDescription The attribute description for this filter.
* @param assertionValue The assertion value for this filter.
* @param subInitialElement The subInitial element for substring filters.
* @param subAnyElements The subAny elements for substring filters.
@@ -104,7 +106,7 @@
*/
public LDAPFilter(FilterType filterType,
ArrayList<RawFilter> filterComponents,
- RawFilter notComponent, String attributeType,
+ RawFilter notComponent, String attributeDescription,
ByteString assertionValue, ByteString subInitialElement,
ArrayList<ByteString> subAnyElements,
ByteString subFinalElement, String matchingRuleID,
@@ -113,7 +115,7 @@
this.filterType = filterType;
this.filterComponents = filterComponents;
this.notComponent = notComponent;
- this.attributeType = attributeType;
+ this.attributeDescription = attributeDescription;
this.assertionValue = assertionValue;
this.subInitialElement = subInitialElement;
this.subAnyElements = subAnyElements;
@@ -145,7 +147,7 @@
}
notComponent = null;
- attributeType = null;
+ attributeDescription = null;
assertionValue = null;
subInitialElement = null;
subAnyElements = null;
@@ -157,7 +159,7 @@
notComponent = new LDAPFilter(filter.getNotComponent());
filterComponents = null;
- attributeType = null;
+ attributeDescription = null;
assertionValue = null;
subInitialElement = null;
subAnyElements = null;
@@ -169,7 +171,7 @@
case GREATER_OR_EQUAL:
case LESS_OR_EQUAL:
case APPROXIMATE_MATCH:
- attributeType = filter.getAttributeType().getNameOrOID();
+ attributeDescription = filter.getAttributeType().getNameOrOID();
assertionValue = filter.getAssertionValue();
filterComponents = null;
@@ -181,7 +183,7 @@
dnAttributes = false;
break;
case SUBSTRING:
- attributeType = filter.getAttributeType().getNameOrOID();
+ attributeDescription = filter.getAttributeType().getNameOrOID();
ByteString bs = filter.getSubInitialElement();
if (bs == null)
@@ -220,7 +222,7 @@
dnAttributes = false;
break;
case PRESENT:
- attributeType = filter.getAttributeType().getNameOrOID();
+ attributeDescription = filter.getAttributeType().getNameOrOID();
filterComponents = null;
notComponent = null;
@@ -238,11 +240,11 @@
AttributeType attrType = filter.getAttributeType();
if (attrType == null)
{
- attributeType = null;
+ attributeDescription = null;
}
else
{
- attributeType = attrType.getNameOrOID();
+ attributeDescription = attrType.getNameOrOID();
}
assertionValue = filter.getAssertionValue();
@@ -423,12 +425,11 @@
}
- // The part of the filter string before the equal sign should be the
- // attribute type. Make sure that the characters it contains are acceptable
- // for attribute types, including those allowed by attribute name
- // exceptions (ASCII letters and digits, the dash, and the underscore). We
- // also need to allow attribute options, which includes the semicolon and
- // the equal sign.
+ // The part of the filter string before the equal sign should be the attribute description.
+ // Make sure that the characters it contains are acceptable for attribute descriptions,
+ // including those allowed by attribute name exceptions
+ // (ASCII letters and digits, the dash, and the underscore).
+ // We also need to allow attribute options, which includes the semicolon and the equal sign.
String attrType = filterString.substring(startPos, attrEndPos);
for (int i=0; i < attrType.length(); i++)
{
@@ -856,10 +857,10 @@
*
* @param filterString The filter string containing the information to
* decode.
- * @param attrType The attribute type for this substring filter
+ * @param attrDesc The attribute description for this substring filter
* component.
* @param equalPos The location of the equal sign separating the
- * attribute type from the value.
+ * attribute description from the value.
* @param endPos The position of the first character after the end of
* the substring value.
*
@@ -869,7 +870,7 @@
* substring filter.
*/
private static LDAPFilter decodeSubstringFilter(String filterString,
- String attrType, int equalPos,
+ String attrDesc, int equalPos,
int endPos)
throws LDAPException
{
@@ -1403,7 +1404,7 @@
}
- return new LDAPFilter(FilterType.SUBSTRING, null, null, attrType, null,
+ return new LDAPFilter(FilterType.SUBSTRING, null, null, attrDesc, null,
subInitial, subAny, subFinal, null, false);
}
@@ -1440,13 +1441,12 @@
// Look at the first character. If it is a colon, then it must be followed
// by either the string "dn" or the matching rule ID. If it is not, then
- // must be the attribute type.
+ // must be the attribute description.
String lowerLeftStr =
toLowerCase(filterString.substring(startPos, equalPos));
if (filterString.charAt(startPos) == ':')
{
- // See if it starts with ":dn". Otherwise, it much be the matching rule
- // ID.
+ // See if it starts with ":dn". Otherwise, it much be the matching rule ID.
if (lowerLeftStr.startsWith(":dn:"))
{
dnAttributes = true;
@@ -1724,16 +1724,16 @@
/**
- * Retrieves the attribute type for this search filter. This will not be
+ * Retrieves the attribute description for this search filter. This will not be
* applicable for AND, OR, or NOT filters.
*
- * @return The attribute type for this search filter, or <CODE>null</CODE> if
+ * @return The attribute description for this search filter, or <CODE>null</CODE> if
* there is none.
*/
@Override
public String getAttributeType()
{
- return attributeType;
+ return attributeDescription;
}
@@ -1886,38 +1886,16 @@
notComp = notComponent.toSearchFilter();
}
-
- AttributeType attrType;
- HashSet<String> options;
- if (attributeType == null)
+ AttributeDescription attrDesc = null;
+ AttributeType attributeType = null;
+ Set<String> options = Collections.emptySet();
+ if (attributeDescription != null)
{
- attrType = null;
- options = null;
+ attrDesc = AttributeDescription.valueOf(attributeDescription);
+ attributeType = attrDesc.getAttributeType();
+ options = toSet(attrDesc);
}
- else
- {
- int semicolonPos = attributeType.indexOf(';');
- if (semicolonPos > 0)
- {
- String baseName = attributeType.substring(0, semicolonPos);
- attrType = DirectoryServer.getAttributeType(baseName);
- options = new HashSet<>();
- StringTokenizer tokenizer =
- new StringTokenizer(attributeType.substring(semicolonPos+1), ";");
- while (tokenizer.hasMoreTokens())
- {
- options.add(tokenizer.nextToken());
- }
- }
- else
- {
- options = null;
- attrType = DirectoryServer.getAttributeType(attributeType);
- }
- }
-
-
- if (assertionValue != null && attrType == null)
+ if (assertionValue != null && attrDesc == null)
{
if (matchingRuleID == null)
{
@@ -1935,12 +1913,21 @@
ArrayList<ByteString> subAnyComps =
subAnyElements != null ? new ArrayList<ByteString>(subAnyElements) : null;
-
- return new SearchFilter(filterType, subComps, notComp, attrType,
- options, assertionValue, subInitialElement, subAnyComps,
+ return new SearchFilter(filterType, subComps, notComp, attributeType, options,
+ assertionValue, subInitialElement, subAnyComps,
subFinalElement, matchingRuleID, dnAttributes);
}
+ private Set<String> toSet(AttributeDescription attrDesc)
+ {
+ LinkedHashSet<String> results = new LinkedHashSet<>();
+ for (String option : attrDesc.getOptions())
+ {
+ results.add(option);
+ }
+ return results;
+ }
+
/**
@@ -1977,14 +1964,14 @@
break;
case EQUALITY:
buffer.append("(");
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
buffer.append("=");
valueToFilterString(buffer, assertionValue);
buffer.append(")");
break;
case SUBSTRING:
buffer.append("(");
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
buffer.append("=");
if (subInitialElement != null)
@@ -2012,26 +1999,26 @@
break;
case GREATER_OR_EQUAL:
buffer.append("(");
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
buffer.append(">=");
valueToFilterString(buffer, assertionValue);
buffer.append(")");
break;
case LESS_OR_EQUAL:
buffer.append("(");
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
buffer.append("<=");
valueToFilterString(buffer, assertionValue);
buffer.append(")");
break;
case PRESENT:
buffer.append("(");
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
buffer.append("=*)");
break;
case APPROXIMATE_MATCH:
buffer.append("(");
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
buffer.append("~=");
valueToFilterString(buffer, assertionValue);
buffer.append(")");
@@ -2039,9 +2026,9 @@
case EXTENSIBLE_MATCH:
buffer.append("(");
- if (attributeType != null)
+ if (attributeDescription != null)
{
- buffer.append(attributeType);
+ buffer.append(attributeDescription);
}
if (dnAttributes)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java
index 49796a7..1a348e9 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java
@@ -19,15 +19,9 @@
import static org.opends.server.replication.plugin.HistAttrModificationKey.*;
import static org.opends.server.util.StaticUtils.*;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
-import org.forgerock.opendj.ldap.schema.AttributeType;
-import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.common.CSN;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
@@ -74,12 +68,12 @@
private final CSN csn;
private final HistAttrModificationKey histKey;
private final String stringValue;
-
+ private boolean attrTypeIsNull;
/**
* This flag indicates that this value was generated to store the last date
* when the entry was renamed.
*/
- private boolean isModDN;
+ private final boolean isModDN;
/**
* Create a new object from the String encoded form.
@@ -91,43 +85,14 @@
{
String[] token = strVal.split(":", 4);
- Set<String> options;
- if (token[0].contains(";"))
- {
- options = new LinkedHashSet<>();
- String[] optionsToken = token[0].split(";");
- int index = 1;
- while (index < optionsToken.length)
- {
- options.add(optionsToken[index]);
- index ++;
- }
- attrString = toLowerCase(optionsToken[0]);
- }
- else
- {
- options = Collections.emptySet();
- attrString = toLowerCase(token[0]);
- }
-
- AttributeType attrType;
- if (attrString.compareTo("dn") != 0)
- {
- // This HistVal was used to store the date when some
- // modifications were done to the entries.
- attrType = DirectoryServer.getAttributeType(attrString);
- }
- else
- {
- // This HistVal is used to store the date when the entry
- // was added to the directory or when it was last renamed.
- attrType = null;
- if (token.length >= 3 && token[2].compareTo("moddn") == 0)
- {
- isModDN = true;
- }
- }
- this.attrDesc = attrType != null ? AttributeDescription.create(attrType, options) : null;
+ attrDesc = AttributeDescription.valueOf(token[0]);
+ attrString = toLowerCase(attrDesc.getNameOrOID());
+ // This HistVal was used to store the date when some
+ // modifications were done to the entries.
+ attrTypeIsNull = attrString.equalsIgnoreCase("dn");
+ // This HistVal is used to store the date when the entry
+ // was added to the directory or when it was last renamed.
+ isModDN = attrTypeIsNull && token.length >= 3 && token[2].compareTo("moddn") == 0;
csn = new CSN(token[1]);
histKey = HistAttrModificationKey.decodeKey(token[2]);
@@ -151,16 +116,6 @@
}
}
- private AttributeType getAttributeType()
- {
- return attrDesc != null ? attrDesc.getAttributeType() : null;
- }
-
- private Iterable<String> getOptions()
- {
- return attrDesc != null ? attrDesc.getOptions() : Collections.<String> emptySet();
- }
-
/**
* Get the String form of the attribute type.
*
@@ -218,8 +173,8 @@
*/
public Modification generateMod()
{
- AttributeBuilder builder = new AttributeBuilder(getAttributeType(), attrString);
- builder.setOptions(getOptions());
+ AttributeBuilder builder = new AttributeBuilder(attrDesc.getAttributeType(), attrString);
+ builder.setOptions(attrDesc.getOptions());
if (histKey != ATTRDEL)
{
@@ -249,7 +204,7 @@
*/
public boolean isADDOperation()
{
- return getAttributeType() == null && !isModDN;
+ return attrTypeIsNull && !isModDN;
}
/**
@@ -260,18 +215,14 @@
*/
public boolean isMODDNOperation()
{
- return getAttributeType() == null && isModDN;
+ return attrTypeIsNull && isModDN;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
- sb.append(attrString);
- for (String option : getOptions())
- {
- sb.append(";").append(option);
- }
+ sb.append(attrDesc);
sb.append(":").append(csn).append(":").append(getModificationType());
if (stringValue != null)
{
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
index d9ccde3..8de4533 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
@@ -3556,7 +3556,6 @@
int endPos;
for (int i=0; i < attrs; i++)
{
-
// First, we have the zero-terminated attribute name.
startPos = entryBuffer.position();
while (entryBuffer.readByte() != 0x00)
@@ -3566,38 +3565,9 @@
String name = entryBuffer.readStringUtf8(endPos - startPos);
entryBuffer.skip(1);
- AttributeType attributeType;
- int semicolonPos = name.indexOf(';');
- if (semicolonPos > 0)
- {
- builder.setAttributeType(name.substring(0, semicolonPos));
- attributeType = builder.getAttributeType();
-
- int nextPos = name.indexOf(';', semicolonPos+1);
- while (nextPos > 0)
- {
- String option = name.substring(semicolonPos+1, nextPos);
- if (option.length() > 0)
- {
- builder.setOption(option);
- }
-
- semicolonPos = nextPos;
- nextPos = name.indexOf(';', semicolonPos+1);
- }
-
- String option = name.substring(semicolonPos+1);
- if (option.length() > 0)
- {
- builder.setOption(option);
- }
- }
- else
- {
- builder.setAttributeType(name);
- attributeType = builder.getAttributeType();
- }
-
+ AttributeDescription attrDesc = AttributeDescription.valueOf(name);
+ builder.setAttributeType(attrDesc.getAttributeType(), attrDesc.getNameOrOID());
+ builder.setOptions(attrDesc.getOptions());
// Next, we have the number of values.
int numValues = entryBuffer.readBERLength();
@@ -3606,15 +3576,13 @@
for (int j=0; j < numValues; j++)
{
int valueLength = entryBuffer.readBERLength();
-
- ByteString valueBytes =
- entryBuffer.readByteSequence(valueLength).toByteString();
- builder.add(valueBytes);
+ builder.add(entryBuffer.readByteSequence(valueLength).toByteString());
}
// Create the attribute and add it to the set of attributes.
Attribute a = builder.toAttribute();
+ AttributeType attributeType = a.getAttributeDescription().getAttributeType();
List<Attribute> attrList = attributes.get(attributeType);
if (attrList == null)
{
@@ -4470,55 +4438,30 @@
continue;
}
- String name;
- Set<String> options;
- int semicolonPos = attrName.indexOf(';');
- if (semicolonPos > 0)
- {
- String tmpName = attrName.substring(0, semicolonPos);
- name = tmpName;
- int nextPos = attrName.indexOf(';', semicolonPos+1);
- options = new HashSet<>();
- while (nextPos > 0)
- {
- options.add(attrName.substring(semicolonPos+1, nextPos));
-
- semicolonPos = nextPos;
- nextPos = attrName.indexOf(';', semicolonPos+1);
- }
- options.add(attrName.substring(semicolonPos+1));
- attrName = tmpName;
- }
- else
- {
- name = attrName;
- options = null;
- }
-
- AttributeType attrType = DirectoryServer.getAttributeType(name);
+ AttributeDescription attrDesc = AttributeDescription.valueOf(attrName);
+ attrName = attrDesc.getNameOrOID();
+ final AttributeType attrType = attrDesc.getAttributeType();
if (attrType.isPlaceHolder())
{
// Unrecognized attribute type - do best effort search.
- for (Map.Entry<AttributeType, List<Attribute>> e :
- userAttributes.entrySet())
+ for (Map.Entry<AttributeType, List<Attribute>> e : userAttributes.entrySet())
{
AttributeType t = e.getKey();
- if (t.hasNameOrOID(name))
+ if (t.hasNameOrOID(attrName))
{
- mergeAttributeLists(e.getValue(), userAttrsCopy, t,
- attrName, options, omitValues, omitReal, omitVirtual);
+ mergeAttributeLists(e.getValue(), userAttrsCopy, attrDesc,
+ omitValues, omitReal, omitVirtual);
continue;
}
}
- for (Map.Entry<AttributeType, List<Attribute>> e :
- operationalAttributes.entrySet())
+ for (Map.Entry<AttributeType, List<Attribute>> e : operationalAttributes.entrySet())
{
AttributeType t = e.getKey();
- if (t.hasNameOrOID(name))
+ if (t.hasNameOrOID(attrName))
{
- mergeAttributeLists(e.getValue(), operationalAttrsCopy,
- t, attrName, options, omitValues, omitReal, omitVirtual);
+ mergeAttributeLists(e.getValue(), operationalAttrsCopy, attrDesc,
+ omitValues, omitReal, omitVirtual);
continue;
}
}
@@ -4556,17 +4499,16 @@
List<Attribute> attrList = getUserAttribute(attrType);
if (!attrList.isEmpty())
{
- mergeAttributeLists(attrList, userAttrsCopy, attrType,
- attrName, options, omitValues, omitReal, omitVirtual);
+ mergeAttributeLists(attrList, userAttrsCopy, attrDesc,
+ omitValues, omitReal, omitVirtual);
}
else
{
attrList = getOperationalAttribute(attrType);
if (!attrList.isEmpty())
{
- mergeAttributeLists(attrList, operationalAttrsCopy,
- attrType, attrName, options, omitValues, omitReal,
- omitVirtual);
+ mergeAttributeLists(attrList, operationalAttrsCopy, attrDesc,
+ omitValues, omitReal, omitVirtual);
}
}
}
@@ -4578,39 +4520,8 @@
operationalAttrsCopy);
}
- /**
- * Copies the provided list of attributes into the destination
- * attribute map according to the provided criteria.
- *
- * @param sourceList
- * The list containing the attributes to be copied.
- * @param destMap
- * The map where the attributes should be copied to.
- * @param attrType
- * The attribute type.
- * @param attrName
- * The user-provided attribute name.
- * @param options
- * The user-provided attribute options.
- * @param omitValues
- * Indicates whether to exclude attribute values.
- * @param omitReal
- * Indicates whether to exclude real attributes.
- * @param omitVirtual
- * Indicates whether to exclude virtual attributes.
- */
private void mergeAttributeLists(List<Attribute> sourceList,
- Map<AttributeType, List<Attribute>> destMap,
- AttributeType attrType, String attrName, Set<String> options,
- boolean omitValues, boolean omitReal, boolean omitVirtual)
- {
- AttributeDescription attrDesc = AttributeDescription.create(attrType, options);
- mergeAttributeLists(sourceList, destMap, attrDesc, attrName, omitValues, omitReal, omitVirtual);
- }
-
- private void mergeAttributeLists(List<Attribute> sourceList,
- Map<AttributeType, List<Attribute>> destMap,
- AttributeDescription attrDesc, String attrName,
+ Map<AttributeType, List<Attribute>> destMap, AttributeDescription attrDesc,
boolean omitValues, boolean omitReal, boolean omitVirtual)
{
if (sourceList == null)
@@ -4618,6 +4529,7 @@
return;
}
+ final String attrName = attrDesc.getNameOrOID();
for (Attribute attribute : sourceList)
{
if (attribute.isEmpty()
--
Gitblit v1.10.0