001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.server.core.api.interceptor.context;
021
022
023import java.util.HashSet;
024import java.util.Set;
025
026import org.apache.commons.lang3.ArrayUtils;
027import org.apache.directory.api.ldap.model.constants.SchemaConstants;
028import org.apache.directory.api.ldap.model.exception.LdapException;
029import org.apache.directory.api.ldap.model.name.Dn;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.api.ldap.model.schema.AttributeTypeOptions;
032import org.apache.directory.api.ldap.model.schema.SchemaManager;
033import org.apache.directory.api.ldap.model.schema.SchemaUtils;
034import org.apache.directory.api.ldap.model.schema.UsageEnum;
035import org.apache.directory.server.core.api.CoreSession;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039
040/**
041 * A context used to store the filter used to manage the Attributes the user
042 * ha srequested. It's used by the Lookup, List and Search operations
043 *
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 */
046public abstract class FilteringOperationContext extends AbstractOperationContext
047{
048    /** The LoggerFactory used by this Interceptor */
049    protected static final Logger LOG = LoggerFactory.getLogger( FilteringOperationContext.class );
050
051    /** A set containing the returning attributeTypesOptions */
052    protected Set<AttributeTypeOptions> returningAttributes;
053
054    /** The set of attributes to return as String */
055    protected String[] returningAttributesString;
056
057    /** A flag set to true if the user has requested all the operational attributes ( "+" )*/
058    private boolean allOperationalAttributes;
059
060    /** A flag set to true if the user has requested all the user attributes ( "*" ) */
061    private boolean allUserAttributes;
062
063    /** A flag set to true if the user has requested no attribute to be returned (1.1) */
064    private boolean noAttributes;
065
066    /** A flag to tell if only the attribute names to be returned. */
067    protected boolean typesOnly = false;
068
069
070    /**
071     * Creates a new instance of FilteringOperationContext.
072     *
073     * @param session The session to use
074     */
075    public FilteringOperationContext( CoreSession session )
076    {
077        // Default to All User Attributes if we don't have any attributes
078        this( session, SchemaConstants.ALL_USER_ATTRIBUTES );
079    }
080
081
082    /**
083     * Creates a new instance of FilteringOperationContext.
084     *
085     * @param session The session to use
086     * @param dn The Dn
087     */
088    public FilteringOperationContext( CoreSession session, Dn dn )
089    {
090        // Default to All User Attributes if we don't have any attributes
091        this( session, dn, SchemaConstants.ALL_USER_ATTRIBUTES );
092    }
093
094
095    /**
096     * Creates a new instance of LookupOperationContext.
097     *
098     * @param session The session to use
099     * @param returningAttributes The attributes to return
100     */
101    public FilteringOperationContext( CoreSession session, String... returningAttributes )
102    {
103        super( session );
104
105        setReturningAttributes( returningAttributes );
106    }
107
108
109    /**
110     * Creates a new instance of LookupOperationContext.
111     *
112     * @param session The session to use
113     * @param dn The Dn
114     * @param returningAttributes The attributes to return
115     */
116    public FilteringOperationContext( CoreSession session, Dn dn, String... returningAttributes )
117    {
118        super( session, dn );
119
120        setReturningAttributes( returningAttributes );
121    }
122
123
124    /**
125     * @return the returningAttributes as a Set of AttributeTypeOptions
126     */
127    public Set<AttributeTypeOptions> getReturningAttributes()
128    {
129        return returningAttributes;
130    }
131
132
133    /**
134     * @return the returning Attributes, as a array of Strings
135     */
136    public String[] getReturningAttributesString()
137    {
138        return returningAttributesString;
139    }
140
141
142    /**
143     * Tells if an attribute is present in the list of attribute to return
144     *
145     * @param schemaManager The SchemaManager instance
146     * @param attribute The attribute we are looking for
147     * @return true if the attribute is present
148     */
149    public boolean contains( SchemaManager schemaManager, String attribute )
150    {
151        if ( isNoAttributes() )
152        {
153            return false;
154        }
155
156        try
157        {
158            AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute );
159
160            return contains( schemaManager, attributeType );
161        }
162        catch ( LdapException le )
163        {
164            return false;
165        }
166    }
167
168
169    /**
170     * Tells if an attribute is present in the list of attribute to return
171     *
172     * @param schemaManager The SchemaManager instance
173     * @param attributeType The attributeType we are looking for
174     * @return true if the attribute is present
175     */
176    public boolean contains( SchemaManager schemaManager, AttributeType attributeType )
177    {
178        if ( isNoAttributes() )
179        {
180            return false;
181        }
182
183        if ( ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) && allUserAttributes )
184        {
185            return true;
186        }
187
188        if ( ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) && allOperationalAttributes )
189        {
190            return true;
191        }
192
193        // Loop on the returningAttribute, as we have two conditions to check
194        if ( returningAttributes == null )
195        {
196            return false;
197        }
198
199        // Shortcut
200        if ( returningAttributes.contains( new AttributeTypeOptions( attributeType ) ) )
201        {
202            return true;
203        }
204
205        // Ok, do it the slow way...
206        for ( AttributeTypeOptions attributeTypeOptions : returningAttributes )
207        {
208            if ( attributeTypeOptions.getAttributeType().equals( attributeType )
209                || attributeTypeOptions.getAttributeType().isAncestorOf( attributeType ) )
210            {
211                return true;
212            }
213        }
214
215        return false;
216    }
217
218
219    public void setReturningAttributes( String... attributeIds )
220    {
221        if ( ( attributeIds != null ) && ( attributeIds.length != 0 ) && ( attributeIds[0] != null ) )
222        {
223            // We have something in the list
224            // first, ignore all the unkown AT and convert the strings to
225            // AttributeTypeOptions
226            returningAttributes = new HashSet<>();
227            Set<String> attributesString = new HashSet<>();
228
229            Set<AttributeTypeOptions> collectedAttributes = collectAttributeTypes( attributeIds );
230
231            // If we have valid, '*' or '+' attributes, we can get rid of the NoAttributes flag
232            if ( !collectedAttributes.isEmpty() || allUserAttributes || allOperationalAttributes )
233            {
234                noAttributes = false;
235            }
236
237            // Now, loop on the list of attributes, and remove all the USER attributes if
238            // we have the '*' attribute, and remove all the OPERATIONAL attributes if we
239            // have the '+' attribute
240            if ( !collectedAttributes.isEmpty() )
241            {
242                for ( AttributeTypeOptions attributeTypeOption : collectedAttributes )
243                {
244                    if ( attributeTypeOption.getAttributeType().isUser() && !allUserAttributes )
245                    {
246                        // We can add the AttributeType in the list of returningAttributeTypes
247                        returningAttributes.add( attributeTypeOption );
248                        attributesString.add( attributeTypeOption.getAttributeType().getOid() );
249                    }
250
251                    if ( attributeTypeOption.getAttributeType().isOperational() && !allOperationalAttributes )
252                    {
253                        // We can add the AttributeType in the list of returningAttributeTypes
254                        returningAttributes.add( attributeTypeOption );
255                        attributesString.add( attributeTypeOption.getAttributeType().getOid() );
256                    }
257                }
258            }
259
260            if ( !attributesString.isEmpty() )
261            {
262                // We have some valid attributes, lt's convert it to String
263                returningAttributesString = attributesString.toArray( ArrayUtils.EMPTY_STRING_ARRAY );
264            }
265            else
266            {
267                // No valid attributes remaining, that means they were all invalid
268                returningAttributesString = ArrayUtils.EMPTY_STRING_ARRAY;
269            }
270        }
271        else
272        {
273            // Nothing in the list : default to '*'
274            allUserAttributes = true;
275            returningAttributesString = ArrayUtils.EMPTY_STRING_ARRAY;
276        }
277    }
278
279
280    private Set<AttributeTypeOptions> collectAttributeTypes( String... attributesIds )
281    {
282        Set<AttributeTypeOptions> collectedAttributes = new HashSet<>();
283
284        if ( ( attributesIds != null ) && ( attributesIds.length != 0 ) )
285        {
286            for ( String returnAttribute : attributesIds )
287            {
288                if ( returnAttribute == null )
289                {
290                    continue;
291                }
292
293                if ( returnAttribute.equals( SchemaConstants.NO_ATTRIBUTE ) )
294                {
295                    noAttributes = true;
296                    continue;
297                }
298
299                if ( returnAttribute.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) )
300                {
301                    allOperationalAttributes = true;
302                    continue;
303                }
304
305                if ( returnAttribute.equals( SchemaConstants.ALL_USER_ATTRIBUTES ) )
306                {
307                    allUserAttributes = true;
308                    continue;
309                }
310
311                try
312                {
313                    String id = SchemaUtils.stripOptions( returnAttribute );
314                    Set<String> options = SchemaUtils.getOptions( returnAttribute );
315
316                    AttributeType attributeType = session.getDirectoryService()
317                        .getSchemaManager().lookupAttributeTypeRegistry( id );
318                    AttributeTypeOptions attrOptions = new AttributeTypeOptions( attributeType, options );
319
320                    collectedAttributes.add( attrOptions );
321                }
322                catch ( LdapException le )
323                {
324                    LOG.warn( "Requested attribute {} does not exist in the schema, it will be ignored",
325                        returnAttribute );
326                    // Unknown attributes should be silently ignored, as RFC 2251 states
327                }
328            }
329        }
330
331        return collectedAttributes;
332    }
333
334
335    /**
336     * @param allOperationalAttributes the allOperationalAttributes to set
337     */
338    public void setAllOperationalAttributes( boolean allOperationalAttributes )
339    {
340        this.allOperationalAttributes = allOperationalAttributes;
341    }
342
343
344    /**
345     * @return The flag telling if the "*" attribute has been used
346     */
347    public boolean isAllUserAttributes()
348    {
349        return allUserAttributes;
350    }
351
352
353    /**
354     * @param allUserAttributes the allUserAttributes to set
355     */
356    public void setAllUserAttributes( boolean allUserAttributes )
357    {
358        this.allUserAttributes = allUserAttributes;
359    }
360
361
362    /**
363     * @return The flag telling if the "+" attribute has been used
364     */
365    public boolean isAllOperationalAttributes()
366    {
367        return allOperationalAttributes;
368    }
369
370
371    /**
372     * @return The flag telling if the "1.1" attribute has been used
373     */
374    public boolean isNoAttributes()
375    {
376        return noAttributes;
377    }
378
379
380    /**
381     * @param noAttributes the noAttributes to set
382     */
383    public void setNoAttributes( boolean noAttributes )
384    {
385        this.noAttributes = noAttributes;
386    }
387
388
389    /**
390     * @return true, if attribute descriptions alone need to be returned
391     */
392    public boolean isTypesOnly()
393    {
394        return typesOnly;
395    }
396
397
398    /**
399     * @param typesOnly true If we want to get back the attributeType only
400     */
401    public void setTypesOnly( boolean typesOnly )
402    {
403        this.typesOnly = typesOnly;
404    }
405
406
407    /**
408     * @see Object#toString()
409     */
410    @Override
411    public String toString()
412    {
413        StringBuilder sb = new StringBuilder();
414
415        sb.append( "FilteringOperationContext for Dn '" );
416        sb.append( dn.getName() ).append( "'" );
417
418        if ( isTypesOnly() )
419        {
420            sb.append( ", type only" );
421        }
422
423        if ( allOperationalAttributes )
424        {
425            sb.append( ", +" );
426        }
427
428        if ( allUserAttributes )
429        {
430            sb.append( ", *" );
431        }
432
433        if ( noAttributes )
434        {
435            sb.append( ", 1.1" );
436        }
437
438        if ( ( returningAttributesString != null ) && ( returningAttributesString.length > 0 ) )
439        {
440            sb.append( ", attributes : <" );
441            boolean isFirst = true;
442
443            for ( String returningAttribute : returningAttributesString )
444            {
445                if ( isFirst )
446                {
447                    isFirst = false;
448                }
449                else
450                {
451                    sb.append( ", " );
452                }
453
454                sb.append( returningAttribute );
455            }
456
457            sb.append( ">" );
458        }
459
460        return sb.toString();
461    }
462}