/*
* Copyright 2013
*
* @author Devin S. Olson (dolson@czarnowski.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
*/
package org.openntf.arpa;
import java.io.Serializable;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.openntf.domino.ext.Name.NameFormat;
import org.openntf.domino.ext.Name.NamePartKey;
import org.openntf.domino.utils.DominoUtils;
/**
* NamePartsMap carries the various component string values that make up a name.
*
* @author Devin S. Olson (dolson@czarnowski.com)
*
*/
public class NamePartsMap extends EnumMap<NamePartKey, String> implements Serializable {
// Enum Key moved to Interface ext-Name (org.openntf.domino.ext.Name.NamePartKey)
public static enum CanonicalKey {
CN("Common Name"), OU("Organizational Unit"), O("Organization"), C("Country Code");
private String _label;
@Override
public String toString() {
return CanonicalKey.class.getName() + ": " + this.name() + "(\"" + this.getLabel() + "\")";
}
/**
* Gets the Label for the Canonical Key
*
* @return Label for the Canonical Key
*/
public String getLabel() {
return this._label;
}
/**
* Sets the Label for the Canonical Key
*
* @param label
* the Label for the Canonical Key
*/
private void setLabel(final String label) {
this._label = label;
}
/**
* Instance Constructor
*
* @param label
* Label for the Canonical Key
*/
private CanonicalKey(final String label) {
this.setLabel(label);
}
}
@SuppressWarnings("unused")
private static final Logger log_ = Logger.getLogger(NamePartsMap.class.getName());
private static final long serialVersionUID = 1L;
private RFC822name _rfc822name;
private NameFormat _nameFormat = NameFormat.FLAT;
/**
* * Zero-Argument Constructor
*/
public NamePartsMap() {
super(NamePartKey.class);
}
/**
* Default Constructor
*
* @param source
* String from which to construct the object
*/
public NamePartsMap(final String string) {
super(NamePartKey.class);
this.parse(string);
}
/**
* Optional Constructor
*
* @param source
* String from which to construct the object
*
* @param rfc822name
* RFC822name for the object.
*/
public NamePartsMap(final String string, final RFC822name rfc822name) {
super(NamePartKey.class);
this.parse(string);
this.setRFC822name(rfc822name);
}
/**
* Default Constructor
*
* @param source
* String from which to construct the object
*
* @param rfc822string
* String from which to construct the RFC822name for the object.
*/
public NamePartsMap(final String string, final String rfc822string) {
super(NamePartKey.class);
this.parse(string);
this.parseRFC82xContent(rfc822string);
}
/*
* ******************************************************************
* ******************************************************************
*
* public methods
*
* ******************************************************************
* ******************************************************************
*/
/**
* Gets the RFC822name for the object
*
* @return the RFC822name
*/
public RFC822name getRFC822name() {
if (null == this._rfc822name) {
this._rfc822name = new RFC822name();
}
return this._rfc822name;
}
/**
* Sets the RFC822name for the object
*
* @param rfc822name
* the RFC822name
*/
private void setRFC822name(final RFC822name rfc822name) {
this._rfc822name = rfc822name;
}
/**
* Clears the object.
*/
@Override
public void clear() {
super.clear();
if (null != this._rfc822name) {
this._rfc822name.clear();
}
}
/**
* Gets the String for the key.
*
* @param key
* Key for the mapped String
*
* @return Mapped String for the key. Empty string "" if no mapping exists.
*
* @see java.util.HashMap#get(Object)
*/
@Override
public String get(final Object key) {
return (key instanceof NamePartKey) ? this.get((NamePartKey) key) : "";
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(NamePartsMap.class.getName());
sb.append(" [");
for (final NamePartKey key : NamePartKey.values()) {
final String s = this.get(key);
if (!ISO.isBlankString(s)) {
sb.append(key.name() + "=" + s);
}
}
sb.append("]");
return sb.toString();
}
/**
* Gets the String for the key.
*
* @param key
* Key for the mapped String
*
* @return Mapped String for the key. Empty string "" if no mapping exists.
*
* @see java.util.HashMap#get(Object)
*/
public String get(final NamePartKey key) {
if (null != key) {
switch (key) {
case Abbreviated: {
final String common = this.get(NamePartKey.Common);
final String ou1 = this.get(NamePartKey.OrgUnit1);
final String ou2 = this.get(NamePartKey.OrgUnit2);
final String ou3 = this.get(NamePartKey.OrgUnit3);
final String ou4 = this.get(NamePartKey.OrgUnit4);
final String organization = this.get(NamePartKey.Organization);
final String country = this.get(NamePartKey.Country);
final StringBuffer sb = new StringBuffer("");
if (!ISO.isBlankString(common)) {
sb.append(common);
}
if (!ISO.isBlankString(ou4)) {
sb.append("/" + ou4);
}
if (!ISO.isBlankString(ou3)) {
sb.append("/" + ou3);
}
if (!ISO.isBlankString(ou2)) {
sb.append("/" + ou2);
}
if (!ISO.isBlankString(ou1)) {
sb.append("/" + ou1);
}
if (!ISO.isBlankString(organization)) {
sb.append("/" + organization);
}
if (!ISO.isBlankString(country)) {
sb.append("/" + country);
}
return sb.toString();
}
case Addr821:
return this.getRFC822name().getAddr821();
case Addr822Comment1:
return this.getRFC822name().getAddr822Comment1();
case Addr822Comment2:
return this.getRFC822name().getAddr822Comment2();
case Addr822Comment3:
return this.getRFC822name().getAddr822Comment3();
case Addr822LocalPart:
return this.getRFC822name().getAddr822LocalPart();
case Addr822Phrase:
return this.getRFC822name().getAddr822Phrase();
case Canonical: {
if (_nameFormat.equals(NameFormat.HIERARCHICAL))
return this.get(NamePartKey.SourceString);
final String common = this.get(NamePartKey.Common);
final String ou1 = this.get(NamePartKey.OrgUnit1);
final String ou2 = this.get(NamePartKey.OrgUnit2);
final String ou3 = this.get(NamePartKey.OrgUnit3);
final String ou4 = this.get(NamePartKey.OrgUnit4);
final String organization = this.get(NamePartKey.Organization);
final String country = this.get(NamePartKey.Country);
final StringBuffer sb = new StringBuffer("");
if (!ISO.isBlankString(common)) {
sb.append("*".equals(common) ? "*" : "CN=" + common);
}
if (!ISO.isBlankString(ou4)) {
sb.append("/OU=" + ou4);
}
if (!ISO.isBlankString(ou3)) {
sb.append("/OU=" + ou3);
}
if (!ISO.isBlankString(ou2)) {
sb.append("/OU=" + ou2);
}
if (!ISO.isBlankString(ou1)) {
sb.append("/OU=" + ou1);
}
if (!ISO.isBlankString(organization)) {
sb.append("/O=" + organization);
}
if (!ISO.isBlankString(country)) {
sb.append("/C=" + country);
}
return sb.toString();
}
default:
final String result = super.get(key);
return (null == result) ? "" : result;
}
}
return "";
}
/**
* Associates the specified String with the specified key in the map.
*
* @param key
* Key for the mapped String
*
* @param value
* String to be associated with the key.
*
* @return Previous value associated with the key. Empty string "" if no mapping exists.
*
* @see java.util.HashMap#put(Object, Object)
*/
@Override
public String put(final NamePartKey key, final String value) {
if (null != key) {
if (ISO.isBlankString(value)) {
if (this.containsKey(key)) {
this.remove(key);
}
return "";
}
switch (key) {
case Abbreviated: {
this.parse(value);
return this.get(NamePartKey.Abbreviated);
}
case Addr821:
return this.getRFC822name().getAddr821();
case Addr822Comment1:
return this.getRFC822name().getAddr822Comment1();
case Addr822Comment2:
return this.getRFC822name().getAddr822Comment2();
case Addr822Comment3:
return this.getRFC822name().getAddr822Comment3();
case Addr822LocalPart:
return this.getRFC822name().getAddr822LocalPart();
case Addr822Phrase:
return this.getRFC822name().getAddr822Phrase();
case Canonical: {
this.parse(value);
return this.get(NamePartKey.Canonical);
}
case IDprefix:
return this.getIDprefix();
default:
final String result = super.put(key, value);
return (null == result) ? "" : result;
}
}
return "";
}
/**
* Gets the IDprefix for the Name.
*
* If an IDprefix does not exist one will be created.
*
* @return IDprefix for the Name
*/
public String getIDprefix() {
String result = super.get(NamePartKey.IDprefix);
if (ISO.isBlankString(result)) {
final String common = this.get(NamePartKey.Common);
if (null != common) {
final String alphanumericandspacacesonly = common.trim().toUpperCase().replaceAll("[^A-Za-z0-9 ]", "");
final int idx = alphanumericandspacacesonly.indexOf(" ");
String firstname = alphanumericandspacacesonly;
String lastname = alphanumericandspacacesonly;
if (idx > 0) {
final String[] chunks = alphanumericandspacacesonly.split(" ");
firstname = chunks[0].trim().replaceAll("[^A-Za-z0-9]", "");
lastname = chunks[chunks.length - 1].replaceAll("[^A-Za-z0-9]", "");
}
final StringBuilder sb = new StringBuilder(firstname.substring(0, 1));
sb.append(lastname.substring(0, 2));
sb.append(lastname.substring(lastname.length() - 1));
while (sb.length() < 4) {
sb.append("X");
}
result = sb.toString();
// use super.put() to avoid endless loop!
super.put(NamePartKey.IDprefix, result);
}
}
return result;
}
/**
* Parses the source string and sets the appropriate RFC822 values.
*
* @param string
* RFC822 source string from which to set the appropriate RFC822 values.
*/
public void parseRFC82xContent(final String string) {
this.getRFC822name().parseRFC82xContent(string);
}
/**
* Indicates whether the object has RFC82xContent
*
* @return Flag indicating if the object has RFC82xContent
*/
public boolean isHasRFC82xContent() {
return (null == this._rfc822name) ? false : this.getRFC822name().isHasRFC82xContent();
}
/**
* Determines if any of the mapped values are equal to the passed in string.
*
* Performs a case-insensitive search.
*
* @param string
* String tom compare values against
*
* @return Flag indicating if any of the values are equal to the string.
*/
public boolean equalsIgnoreCase(final String string) {
if (!ISO.isBlankString(string)) {
for (final String s : this.values()) {
if (string.equalsIgnoreCase(s)) {
return true;
}
}
return this.getRFC822name().equalsIgnoreCase(string);
}
return false;
}
/**
* Determines if any of the mapped values begin with the prefix.
*
* @param prefix
* Value to compare to the mapped values.
*
* @param casesensitive
* Flag indicating if Case-Sensitive comparisons should be enforced.
*
* @return Flag indicating if any of the mapped values begin with the prefix
*/
public boolean startsWith(final String prefix, final boolean casesensitive) {
if (!ISO.isBlankString(prefix)) {
if (casesensitive) {
for (final String s : this.values()) {
if ((null != s) && s.startsWith(prefix)) {
return true;
}
}
} else {
for (final String s : this.values()) {
if (ISO.startsWithIgnoreCase(s, prefix)) {
return true;
}
}
}
}
return false;
} /*
* ******************************************************************
* ******************************************************************
*
* private methods
*
* ******************************************************************
* ******************************************************************
*/
/**
* Retrieves and sets the various name values by parsing an input source string.
*
* @param string
* String from which to parse the name values.
*/
private boolean parse(final String string) {
return this.parse(string, true);
}
/**
* Retrieves and sets the various name values by parsing an input source string.
*
* @param string
* String from which to parse the name values.
*
* @param allowRecursion
* Flag indicating if this method is allowed to recursively call itself.
*/
private boolean parse(final String string, final boolean allowRecursion) {
if (DominoUtils.isHierarchicalName(string)) {
_nameFormat = NameFormat.DOMINO;
DominoUtils.parseNamesPartMap(string, this); //TODO NTF should replace later with just a thrown exception if it's not hierarchical
} else {
try {
String common = "";
final String[] ous = new String[] { "", "", "", "" };
String organization = "";
String country = "";
if (!ISO.isBlankString(string)) {
if (ISO.PatternRFC822.matcher(string).matches()) {
this.parseRFC82xContent(string);
if (allowRecursion) {
final String phrase = this.getRFC822name().getAddr822Phrase();
return this.parse((phrase.indexOf('/') < 0) ? this.getRFC822name().getAddr822PhraseFirstLast() : phrase, false);
}
return false;
}
if (string.indexOf('/') < 0) {
common = string;
} else {
// break the source into component words and parse them
final String[] words = string.split("/");
if (words.length > 0) {
int idx = 0;
if (string.indexOf('=') > 0) {
// use canonical logic
try {
TreeMap<Integer, String> undefinedValues = null;
int Oidx = -1;
int Cidx = -1;
for (int i = (words.length - 1); i >= 0; i--) {
final String word = words[i].trim();
if (word.indexOf('=') > 0) {
final String[] nibbles = word.split("=");
if (nibbles.length > 1) {
final String key = nibbles[0];
final String value = nibbles[1];
if (CanonicalKey.C.name().equalsIgnoreCase(key)) {
country = value;
Cidx = i;
} else if (CanonicalKey.O.name().equalsIgnoreCase(key)) {
organization = value;
Oidx = i;
} else if (CanonicalKey.OU.name().equalsIgnoreCase(key)) {
if (idx < 4) {
ous[idx] = value;
}
idx++;
} else if (CanonicalKey.CN.name().equalsIgnoreCase(key)) {
common = value;
}
} else {
throw new RuntimeException("Cannot Parse Word: \"" + word + "\", Source String: \""
+ string + "\"");
}
} else {
// no '=' in word
if (null == undefinedValues) {
undefinedValues = new TreeMap<Integer, String>();
}
undefinedValues.put(new Integer(i), word);
}
}
if (null != undefinedValues) {
// at least one undefined value (such as a
// wildcard) exists.
final Iterator<Map.Entry<Integer, String>> it = undefinedValues.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry<Integer, String> entry = it.next();
final int idxEntry = entry.getKey().intValue();
if (0 == idxEntry) {
if (ISO.isBlankString(common)) {
common = entry.getValue();
} else {
for (String s : ous) {
if (ISO.isBlankString(s)) {
s = entry.getValue();
break;
}
}
}
} else if ((Cidx < 0) && (idxEntry == (words.length - 1))) {
if (ISO.isBlankString(organization)) {
organization = entry.getValue();
} else {
country = entry.getValue();
}
} else if (idxEntry == Cidx) {
organization = entry.getValue();
} else if (idxEntry == Oidx) {
for (String orgunit : ous) {
if (ISO.isBlankString(orgunit)) {
orgunit = entry.getValue();
break;
}
}
}
}
}
} catch (final Exception e) {
DominoUtils.handleException(e, "Source String: \"" + string + "\"");
}
} else {
// use abbreviated logic
common = words[0].trim();
if (words.length > 1) {
int orgpos = (words.length - 1);
organization = words[orgpos];
if (ISO.isCountryCode2(organization)) {
// organization could be a country code,
if (orgpos > 1) {
// Treat organization as a country code
// and
// re-aquire the organization
country = organization;
orgpos--;
organization = words[orgpos];
}
}
int oupos = orgpos - 1;
while (oupos > 0) {
ous[idx] = words[oupos];
oupos--;
idx++;
if (idx > 3) {
break;
}
}
}
}
}
}
}
this.put(NamePartKey.Common, common);
this.put(NamePartKey.OrgUnit1, ous[0]);
this.put(NamePartKey.OrgUnit2, ous[1]);
this.put(NamePartKey.OrgUnit3, ous[2]);
this.put(NamePartKey.OrgUnit4, ous[3]);
this.put(NamePartKey.Organization, organization);
this.put(NamePartKey.Country, country);
return true;
} catch (final Exception e) {
DominoUtils.handleException(e, "Source String: \"" + string + "\"");
}
}
return false;
}
public boolean isHiearchical() {
return _nameFormat == NameFormat.DOMINO || _nameFormat == NameFormat.HIERARCHICAL;
}
public NameFormat getNameFormat() {
return _nameFormat;
}
}