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.number;
021
022
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.UUID;
029import java.util.concurrent.atomic.AtomicInteger;
030
031import org.apache.directory.api.ldap.model.constants.SchemaConstants;
032import org.apache.directory.api.ldap.model.entry.Attribute;
033import org.apache.directory.api.ldap.model.entry.DefaultEntry;
034import org.apache.directory.api.ldap.model.entry.DefaultModification;
035import org.apache.directory.api.ldap.model.entry.Entry;
036import org.apache.directory.api.ldap.model.entry.Modification;
037import org.apache.directory.api.ldap.model.entry.ModificationOperation;
038import org.apache.directory.api.ldap.model.exception.LdapException;
039import org.apache.directory.api.ldap.model.exception.LdapOtherException;
040import org.apache.directory.api.ldap.model.name.Dn;
041import org.apache.directory.api.ldap.model.schema.MatchingRule;
042import org.apache.directory.server.core.api.DirectoryService;
043import org.apache.directory.server.core.api.entry.ClonedServerEntry;
044import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
045import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
046import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
047import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
048import org.apache.directory.server.core.api.partition.Partition;
049import org.apache.directory.server.core.api.partition.PartitionTxn;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053
054/**
055 * An interceptor to increment any attribute with integer matching rule
056 *
057 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
058 */
059public class NumberIncrementingInterceptor extends BaseInterceptor
060{
061    /** A {@link Logger} for this class */
062    private static final Logger LOG = LoggerFactory.getLogger( NumberIncrementingInterceptor.class );
063
064    /** the DN of the holder entry */
065    private Dn numberHolder;
066    
067    /** a map of integer attribute and it's present value */
068    private Map<String, AtomicInteger> incMap = new HashMap<>();
069    
070    
071    @Override
072    public void init( DirectoryService directoryService ) throws LdapException
073    {
074        super.init( directoryService );
075        
076        numberHolder = new Dn( schemaManager, "ou=autoIncDataHolder,ou=system" );
077        
078        Partition systemPartition = directoryService.getSystemPartition();
079        
080        LookupOperationContext lookupContext = new LookupOperationContext( directoryService.getAdminSession(), numberHolder, SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 
081        lookupContext.setPartition( systemPartition );
082
083        Entry entry;
084        
085        try ( PartitionTxn partitionTxn = systemPartition.beginReadTransaction() )
086        {
087            lookupContext.setTransaction( partitionTxn );
088
089            entry = systemPartition.lookup( lookupContext );
090        }
091        catch ( IOException ioe )
092        {
093            throw new LdapOtherException( ioe.getMessage(), ioe );
094        }
095
096        if ( entry == null )
097        {
098            //FIXME make sure this entry addition gets replicated
099            entry = new DefaultEntry( schemaManager );
100            entry.setDn( numberHolder );
101            entry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
102            entry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.EXTENSIBLE_OBJECT_OC );
103            entry.add( SchemaConstants.OU_AT, numberHolder.getRdn().getValue() );
104            entry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
105            entry.add( SchemaConstants.ENTRY_CSN_AT, directoryService.getCSN().toString() );
106            
107            AddOperationContext addContext = new AddOperationContext( directoryService.getAdminSession() );
108            addContext.setDn( numberHolder );
109            addContext.setEntry( new ClonedServerEntry( entry ) );
110            addContext.setPartition( systemPartition );
111            PartitionTxn partitionTxn = null;
112            
113            try
114            {
115                partitionTxn = systemPartition.beginWriteTransaction();
116                addContext.setTransaction( partitionTxn );
117                
118                LOG.debug( "Adding container entry to hold numeric attribute values" );
119                systemPartition.add( addContext );
120                partitionTxn.commit();
121            }
122            catch ( LdapException le )
123            {
124                if ( partitionTxn != null )
125                {
126                    try
127                    { 
128                        partitionTxn.abort();
129                    }
130                    catch ( IOException ioe )
131                    {
132                        throw new LdapOtherException( ioe.getMessage(), ioe );
133                    }
134                }
135                
136                throw le;
137            }
138            catch ( IOException ioe )
139            {
140                try
141                { 
142                    partitionTxn.abort();
143                }
144                catch ( IOException ioe2 )
145                {
146                    throw new LdapOtherException( ioe2.getMessage(), ioe2 );
147                }
148
149                throw new LdapOtherException( ioe.getMessage(), ioe );
150            }
151        }
152        else
153        {
154            for ( Attribute at : entry )
155            {
156                MatchingRule mr = at.getAttributeType().getEquality();
157                
158                if ( ( mr != null ) && SchemaConstants.INTEGER_MATCH_MR_OID.equals( mr.getOid() ) )
159                {
160                    int t = Integer.parseInt( at.getString() );
161                    incMap.put( at.getId(), new AtomicInteger( t ) );
162                }
163            }
164        }
165    }
166
167
168    /**
169     * {@inheritDoc}
170     */
171    @Override
172    public void add( AddOperationContext addContext ) throws LdapException
173    {
174        LOG.debug( ">>> Entering into the number incrementing interceptor, addRequest" );
175
176        if ( addContext.isReplEvent() )
177        {
178            // Nope, go on.
179            next( addContext );
180            return;
181        }
182
183        Entry entry = addContext.getEntry();
184
185        List<Attribute> lst = new ArrayList<>();
186        
187        for ( String oid : incMap.keySet() )
188        {
189            Attribute at = entry.get( oid );
190            if ( at != null )
191            {
192                lst.add( at );
193            }
194        }
195        
196        if ( lst.isEmpty() )
197        {
198            next( addContext );
199            return;
200        }
201
202        for ( Attribute at : lst )
203        {
204            int stored = incMap.get( at.getId() ).get();
205            at.clear();
206            at.add( String.valueOf( stored + 1 ) );
207        }
208        
209        // Ok, we are golden.
210        next( addContext );
211
212        ModifyOperationContext bindModCtx = new ModifyOperationContext( directoryService.getAdminSession() );
213        bindModCtx.setDn( numberHolder );
214        bindModCtx.setPushToEvtInterceptor( true );
215
216        List<Modification> mods = new ArrayList<>();
217
218        for ( Attribute at : lst )
219        {
220            AtomicInteger ai = incMap.get( at.getId() );
221            ai.set( ai.get() + 1 );
222            
223            Modification mod = new DefaultModification();
224            mod.setOperation( ModificationOperation.REPLACE_ATTRIBUTE );
225            mod.setAttribute( at );
226            
227            mods.add( mod );
228        }
229        
230        bindModCtx.setModItems( mods );
231        
232        directoryService.getPartitionNexus().modify( bindModCtx );
233        
234        LOG.debug( "Successfully updated numeric attribute in {}", numberHolder );
235    }
236}