PageRenderTime 55ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/java/org/springframework/ldap/core/support/DefaultIncrementalAttributesMapper.java

https://gitlab.com/lucky.sutanto/spring-ldap
Java | 440 lines | 231 code | 59 blank | 150 comment | 28 complexity | 6cc7e0bd23e82e29a73dc94e893bcf29 MD5 | raw file
  1. /*
  2. * Copyright 2005-2013 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.ldap.core.support;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19. import org.springframework.ldap.core.IncrementalAttributesMapper;
  20. import org.springframework.ldap.core.LdapOperations;
  21. import org.springframework.ldap.support.LdapUtils;
  22. import javax.naming.Name;
  23. import javax.naming.NamingEnumeration;
  24. import javax.naming.NamingException;
  25. import javax.naming.directory.Attribute;
  26. import javax.naming.directory.Attributes;
  27. import javax.naming.directory.BasicAttribute;
  28. import javax.naming.directory.BasicAttributes;
  29. import java.util.ArrayList;
  30. import java.util.Collections;
  31. import java.util.HashSet;
  32. import java.util.LinkedHashMap;
  33. import java.util.LinkedHashSet;
  34. import java.util.LinkedList;
  35. import java.util.List;
  36. import java.util.Map;
  37. import java.util.Set;
  38. /**
  39. * Utility class that helps with reading all attribute values from Active Directory using <em>Incremental Retrieval of
  40. * Multi-valued Properties</em>.
  41. * <p>Example usage of this attribute mapper:
  42. * <pre>
  43. * List values = DefaultIncrementalAttributeMapper.lookupAttributeValues(ldapTemplate, theDn, "oneAttribute");
  44. * Attributes attrs = DefaultIncrementalAttributeMapper.lookupAttributeValues(ldapTemplate, theDn, new Object[]{"oneAttribute", "anotherAttribute"});
  45. * </pre>
  46. * For greater control, e.g. explicitly specifying the requested page size, create and use an instance yourself:
  47. * <pre>
  48. *
  49. * IncrementalAttributesMapper incrementalAttributeMapper = new DefaultIncrementalAttributeMapper(10, "someAttribute");
  50. * while (incrementalAttributeMapper.hasMore()) {
  51. * ldap.lookup(entrDn, incrementalAttributeMapper.getAttributesForLookup(), incrementalAttributeMapper);
  52. * }
  53. *
  54. * List values = incrementalAttributeMapper.getValues("someAttribute");
  55. * </pre>
  56. * <p>
  57. * <b>NOTE:</b> Instances of this class are highly stateful and must not be reused or shared between threads in any way.
  58. * <p>
  59. * <b>NOTE:</b> Instances of this class can only be used with <b>lookups</b>. No support is given for searches.
  60. * <p>
  61. *
  62. * @author Marius Scurtescu
  63. * @author Mattias Hellborg Arthursson
  64. * @see <a href="http://www.watersprings.org/pub/id/draft-kashi-incremental-00.txt">Incremental Retrieval of Multi-valued Properties</a>
  65. * @see #lookupAttributes(org.springframework.ldap.core.LdapOperations, javax.naming.Name, String[])
  66. * @see #lookupAttributeValues(org.springframework.ldap.core.LdapOperations, javax.naming.Name, String)
  67. * @since 1.3.2
  68. */
  69. public class DefaultIncrementalAttributesMapper implements IncrementalAttributesMapper<DefaultIncrementalAttributesMapper> {
  70. private final static Logger LOG = LoggerFactory.getLogger(DefaultIncrementalAttributesMapper.class);
  71. private Map<String, IncrementalAttributeState> stateMap = new LinkedHashMap<String, IncrementalAttributeState>();
  72. private Set<String> rangedAttributesInNextIteration = new LinkedHashSet<String>();
  73. /**
  74. * This guy will be used when an unmapped attribute is encountered. This really should never happen,
  75. * but this saves us a number of null checks.
  76. */
  77. private static final IncrementalAttributeState NOT_FOUND_ATTRIBUTE_STATE = new IncrementalAttributeState() {
  78. @Override
  79. public String getRequestedAttributeName() {
  80. throw new UnsupportedOperationException("This method should never be called");
  81. }
  82. @Override
  83. public boolean hasMore() {
  84. return false;
  85. }
  86. @Override
  87. public void calculateNextRange(RangeOption responseRange) {
  88. // Nothing to do here
  89. }
  90. @Override
  91. public String getAttributeNameForQuery() {
  92. throw new UnsupportedOperationException("This method should never be called");
  93. }
  94. @Override
  95. public void processValues(Attributes attributes, String attributeName) throws NamingException {
  96. // Nothing to do here
  97. }
  98. @Override
  99. public List<Object> getValues() {
  100. return null;
  101. }
  102. };
  103. /**
  104. * Create an instance for the requested attribute.
  105. *
  106. * @param attributeName the name of the attribute that this instance handles.
  107. * This is the attribute name that will be requested, and whose
  108. * values are managed.
  109. */
  110. public DefaultIncrementalAttributesMapper(String attributeName) {
  111. this(RangeOption.TERMINAL_END_OF_RANGE, attributeName);
  112. }
  113. /**
  114. * Create an instance for the requested attributes.
  115. *
  116. * @param attributeNames the name of the attributes that this instance handles.
  117. * These are the attribute names that will be requested, and whose
  118. * values are managed.
  119. */
  120. public DefaultIncrementalAttributesMapper(String[] attributeNames) {
  121. this(RangeOption.TERMINAL_END_OF_RANGE, attributeNames);
  122. }
  123. /**
  124. * Create an instance for the requested attribute with a specific page size.
  125. *
  126. * @param pageSize the requested page size that will be included in range query attribute names.
  127. * @param attributeName the name of the attribute that this instance handles.
  128. * This is the attribute name that will be requested, and whose
  129. * values are managed.
  130. */
  131. public DefaultIncrementalAttributesMapper(int pageSize, String attributeName) {
  132. this(pageSize, new String[]{attributeName});
  133. }
  134. /**
  135. * Create an instance for the requested attributes with a specific page size.
  136. *
  137. * @param pageSize the requested page size that will be included in range query attribute names.
  138. * @param attributeNames the name of the attributes that this instance handles.
  139. * These are the attribute names that will be requested, and whose
  140. * values are managed.
  141. */
  142. public DefaultIncrementalAttributesMapper(int pageSize, String[] attributeNames) {
  143. for (String attributeName : attributeNames) {
  144. this.stateMap.put(attributeName, new DefaultIncrementalAttributeState(attributeName, pageSize));
  145. this.rangedAttributesInNextIteration.add(attributeName);
  146. }
  147. }
  148. @Override
  149. public final DefaultIncrementalAttributesMapper mapFromAttributes(Attributes attributes) throws NamingException {
  150. if (!hasMore()) {
  151. throw new IllegalStateException("No more attributes!");
  152. }
  153. // Reset the affected attributes.
  154. rangedAttributesInNextIteration = new HashSet<String>();
  155. NamingEnumeration<String> attributeNameEnum = attributes.getIDs();
  156. while (attributeNameEnum.hasMore()) {
  157. String attributeName = attributeNameEnum.next();
  158. String[] attributeNameSplit = attributeName.split(";");
  159. IncrementalAttributeState state = getState(attributeNameSplit[0]);
  160. if (attributeNameSplit.length == 1) {
  161. // No range specification for this attribute
  162. state.processValues(attributes, attributeName);
  163. } else {
  164. for (String option : attributeNameSplit) {
  165. RangeOption responseRange = RangeOption.parse(option);
  166. if (responseRange != null) {
  167. state.processValues(attributes, attributeName);
  168. state.calculateNextRange(responseRange);
  169. if (state.hasMore()) {
  170. rangedAttributesInNextIteration.add(state.getRequestedAttributeName());
  171. }
  172. }
  173. }
  174. }
  175. }
  176. return this;
  177. }
  178. private IncrementalAttributeState getState(String attributeName) {
  179. Object mappedState = stateMap.get(attributeName);
  180. if (mappedState == null) {
  181. LOG.warn("Attribute '" + attributeName + "' is not handled by this instance");
  182. mappedState = NOT_FOUND_ATTRIBUTE_STATE;
  183. }
  184. return (IncrementalAttributeState) mappedState;
  185. }
  186. @Override
  187. public final List<Object> getValues(String attributeName) {
  188. return getState(attributeName).getValues();
  189. }
  190. @Override
  191. public final Attributes getCollectedAttributes() {
  192. BasicAttributes attributes = new BasicAttributes();
  193. Set<String> attributeNames = stateMap.keySet();
  194. for (String attributeName : attributeNames) {
  195. BasicAttribute oneAttribute = new BasicAttribute(attributeName);
  196. List<Object> values = getValues(attributeName);
  197. if (values != null) {
  198. for (Object oneValue : values) {
  199. oneAttribute.add(oneValue);
  200. }
  201. }
  202. attributes.put(oneAttribute);
  203. }
  204. return attributes;
  205. }
  206. @Override
  207. public final boolean hasMore() {
  208. return rangedAttributesInNextIteration.size() > 0;
  209. }
  210. @Override
  211. public final String[] getAttributesForLookup() {
  212. String[] result = new String[rangedAttributesInNextIteration.size()];
  213. int index = 0;
  214. for (String next : rangedAttributesInNextIteration) {
  215. IncrementalAttributeState state = stateMap.get(next);
  216. result[index++] = state.getAttributeNameForQuery();
  217. }
  218. return result;
  219. }
  220. /**
  221. * Lookup all values for the specified attribute, looping through the results incrementally if necessary.
  222. *
  223. * @param ldapOperations The instance to use for performing the actual lookup.
  224. * @param dn The distinguished name of the object to find.
  225. * @param attribute name of the attribute to request.
  226. * @return an Attributes instance, populated with all found values for the requested attribute.
  227. * Never <code>null</code>, though the actual attribute may not be set if it was not
  228. * set on the requested object.
  229. */
  230. public static Attributes lookupAttributes(LdapOperations ldapOperations, String dn, String attribute) {
  231. return lookupAttributes(ldapOperations, LdapUtils.newLdapName(dn), attribute);
  232. }
  233. /**
  234. * Lookup all values for the specified attributes, looping through the results incrementally if necessary.
  235. *
  236. * @param ldapOperations The instance to use for performing the actual lookup.
  237. * @param dn The distinguished name of the object to find.
  238. * @param attributes names of the attributes to request.
  239. * @return an Attributes instance, populated with all found values for the requested attributes.
  240. * Never <code>null</code>, though the actual attributes may not be set if they was not
  241. * set on the requested object.
  242. */
  243. public static Attributes lookupAttributes(LdapOperations ldapOperations, String dn, String[] attributes) {
  244. return lookupAttributes(ldapOperations, LdapUtils.newLdapName(dn), attributes);
  245. }
  246. /**
  247. * Lookup all values for the specified attribute, looping through the results incrementally if necessary.
  248. *
  249. * @param ldapOperations The instance to use for performing the actual lookup.
  250. * @param dn The distinguished name of the object to find.
  251. * @param attribute name of the attribute to request.
  252. * @return an Attributes instance, populated with all found values for the requested attribute.
  253. * Never <code>null</code>, though the actual attribute may not be set if it was not
  254. * set on the requested object.
  255. */
  256. public static Attributes lookupAttributes(LdapOperations ldapOperations, Name dn, String attribute) {
  257. return lookupAttributes(ldapOperations, dn, new String[]{attribute});
  258. }
  259. /**
  260. * Lookup all values for the specified attributes, looping through the results incrementally if necessary.
  261. *
  262. * @param ldapOperations The instance to use for performing the actual lookup.
  263. * @param dn The distinguished name of the object to find.
  264. * @param attributes names of the attributes to request.
  265. * @return an Attributes instance, populated with all found values for the requested attributes.
  266. * Never <code>null</code>, though the actual attributes may not be set if they was not
  267. * set on the requested object.
  268. */
  269. public static Attributes lookupAttributes(LdapOperations ldapOperations, Name dn, String[] attributes) {
  270. return loopForAllAttributeValues(ldapOperations, dn, attributes).getCollectedAttributes();
  271. }
  272. /**
  273. * Lookup all values for the specified attribute, looping through the results incrementally if necessary.
  274. *
  275. * @param ldapOperations The instance to use for performing the actual lookup.
  276. * @param dn The distinguished name of the object to find.
  277. * @param attribute name of the attribute to request.
  278. * @return a list with all attribute values found for the requested attribute.
  279. * Never <code>null</code>, an empty list indicates that the attribute was not set or empty.
  280. */
  281. public static List<Object> lookupAttributeValues(LdapOperations ldapOperations, String dn, String attribute) {
  282. return lookupAttributeValues(ldapOperations, LdapUtils.newLdapName(dn), attribute);
  283. }
  284. /**
  285. * Lookup all values for the specified attribute, looping through the results incrementally if necessary.
  286. *
  287. * @param ldapOperations The instance to use for performing the actual lookup.
  288. * @param dn The distinguished name of the object to find.
  289. * @param attribute name of the attribute to request.
  290. * @return a list with all attribute values found for the requested attribute.
  291. * Never <code>null</code>, an empty list indicates that the attribute was not set or empty.
  292. */
  293. public static List<Object> lookupAttributeValues(LdapOperations ldapOperations, Name dn, String attribute) {
  294. List<Object> values = loopForAllAttributeValues(ldapOperations, dn, new String[]{attribute}).getValues(attribute);
  295. if(values == null) {
  296. values = Collections.emptyList();
  297. }
  298. return values;
  299. }
  300. private static DefaultIncrementalAttributesMapper loopForAllAttributeValues(LdapOperations ldapOperations, Name dn, String[] attributes) {
  301. DefaultIncrementalAttributesMapper mapper = new DefaultIncrementalAttributesMapper(attributes);
  302. while (mapper.hasMore()) {
  303. ldapOperations.lookup(dn, mapper.getAttributesForLookup(), mapper);
  304. }
  305. return mapper;
  306. }
  307. /**
  308. * This class keeps track of the state of an individual attribute in the process of collecting
  309. * multi-value attributes using ranges. Holds the values collected thus far, the next applicable range,
  310. * and the actual (requested) attribute name.
  311. */
  312. private static final class DefaultIncrementalAttributeState implements IncrementalAttributeState {
  313. private final String actualAttributeName;
  314. private List<Object> values = null;
  315. private final int pageSize;
  316. boolean more = true;
  317. private RangeOption requestRange;
  318. private DefaultIncrementalAttributeState(String actualAttributeName, int pageSize) {
  319. this.actualAttributeName = actualAttributeName;
  320. this.pageSize = pageSize;
  321. this.requestRange = new RangeOption(0, pageSize);
  322. }
  323. @Override
  324. public boolean hasMore() {
  325. return more;
  326. }
  327. @Override
  328. public String getRequestedAttributeName() {
  329. return actualAttributeName;
  330. }
  331. @Override
  332. public void calculateNextRange(RangeOption responseRange) {
  333. more = requestRange.compareTo(responseRange) > 0;
  334. if (more) {
  335. requestRange = responseRange.nextRange(pageSize);
  336. }
  337. }
  338. @Override
  339. public String getAttributeNameForQuery() {
  340. StringBuilder attributeBuilder = new StringBuilder(actualAttributeName);
  341. if (!(requestRange.isFullRange())) {
  342. attributeBuilder.append(';');
  343. requestRange.appendTo(attributeBuilder);
  344. }
  345. return attributeBuilder.toString();
  346. }
  347. @Override
  348. public void processValues(Attributes attributes, String attributeName) throws NamingException {
  349. Attribute attribute = attributes.get(attributeName);
  350. NamingEnumeration<?> valueEnum = attribute.getAll();
  351. initValuesIfApplicable();
  352. while (valueEnum.hasMore()) {
  353. values.add(valueEnum.next());
  354. }
  355. }
  356. private void initValuesIfApplicable() {
  357. if (values == null) {
  358. values = new LinkedList<Object>();
  359. }
  360. }
  361. @Override
  362. public List<Object> getValues() {
  363. if (values != null) {
  364. return new ArrayList<Object>(values);
  365. } else {
  366. return null;
  367. }
  368. }
  369. }
  370. /**
  371. * @author Mattias Hellborg Arthursson
  372. */
  373. private interface IncrementalAttributeState {
  374. boolean hasMore();
  375. void calculateNextRange(RangeOption responseRange);
  376. String getAttributeNameForQuery();
  377. String getRequestedAttributeName();
  378. void processValues(Attributes attributes, String attributeName) throws NamingException;
  379. List<Object> getValues();
  380. }
  381. }