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 */
020
021package org.apache.directory.api.ldap.model.ldif.anonymizer;
022
023
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.directory.api.ldap.model.entry.Attribute;
030import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
031import org.apache.directory.api.ldap.model.entry.StringValue;
032import org.apache.directory.api.ldap.model.entry.Value;
033import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
034
035
036/**
037 * A default anonymizer for attributes that is an Integer. the initial value is randomized
038 *
039 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
040 */
041public class IntegerAnonymizer extends AbstractAnonymizer<String>
042{
043    /** The latest anonymized Integer value map */
044    private Map<Integer, String> latestIntegerMap;
045
046    /**
047     * Creates a new instance of IntegerAnonymizer.
048     */
049    public IntegerAnonymizer()
050    {
051        latestIntegerMap = new HashMap<>();
052    }
053
054    
055    /**
056     * Creates a new instance of IntegerAnonymizer.
057     * 
058     * @param latestIntegerMap The map containing the latest integer value for each length 
059     */
060    public IntegerAnonymizer( Map<Integer, String> latestIntegerMap )
061    {
062        if ( latestIntegerMap == null ) 
063        {
064            this.latestIntegerMap = new HashMap<>();
065        }
066        else
067        {
068            this.latestIntegerMap = latestIntegerMap;
069        }
070    }
071
072    /**
073     * Anonymize an attribute using pure random values (either chars of bytes, depending on the Attribute type)
074     */
075    @Override
076    public Attribute anonymize( Map<Value<String>, Value<String>> valueMap, Set<Value<String>> valueSet, Attribute attribute )
077    {
078        Attribute result = new DefaultAttribute( attribute.getAttributeType() );
079
080        for ( Value<?> value : attribute )
081        {
082            if ( value instanceof StringValue )
083            {
084                Value<String> anonymized =  valueMap.get( value );
085                
086                if ( anonymized != null )
087                {
088                    try
089                    {
090                        result.add( anonymized );
091                    }
092                    catch ( LdapInvalidAttributeValueException e )
093                    {
094                        // Handle me...
095                    }
096                }
097                else
098                {
099                    String strValue = value.getNormValue().toString();
100                    String newValue = computeNewIntegerValue( strValue );
101    
102                    try
103                    {
104                        result.add( newValue );
105                        Value<String> anonValue = new StringValue( attribute.getAttributeType(), newValue );
106                        valueMap.put( ( Value<String> ) value, anonValue );
107                        valueSet.add( anonValue );
108                    }
109                    catch ( LdapInvalidAttributeValueException e )
110                    {
111                        // TODO : handle that
112                    }
113                }
114            }
115        }
116
117        return result;
118    }
119    
120
121    /**
122     * @return The Map containing the latest anonymized value for each integer
123     */
124    public Map<Integer, String> getLatestIntegerMap()
125    {
126        return latestIntegerMap;
127    }
128    
129    
130    /**
131     * Set the Map containing anonymized integers
132     * @param latestIntegerMap The Map containing the latest anonymized value for each integer
133     */
134    public void setLatestIntegerMap( Map<Integer, String> latestIntegerMap )
135    {
136        this.latestIntegerMap = latestIntegerMap;
137    }
138
139    
140    /**
141     * Compute the next Integer value
142     *
143     * @param valStr The original value
144     * @return The anonymized value
145     */
146    private String computeNewIntegerValue( String valStr )
147    {
148        int length = valStr.length();
149        String latestInteger = latestIntegerMap.get( length );
150        
151        if ( latestInteger == null )
152        {
153            // No previous value : create a new one
154            char[] newValue = new char[length];
155            
156            Arrays.fill( newValue, '9' );
157            
158            String anonymizedValue = new String( newValue );
159            latestIntegerMap.put( length, anonymizedValue );
160            
161            return anonymizedValue;
162        }
163        else
164        {
165            // Compute a new value
166            char[] latest = latestInteger.toCharArray();
167            boolean overflow = true;
168            
169            for ( int i = length - 1; i >= 0; i-- )
170            {
171                if ( latest[i] == '0' )
172                {
173                    latest[i] = '9';
174                }
175                else
176                {
177                    latest[i]--;
178                    overflow = false;
179                    break;
180                }
181            }
182            
183            // Corner case : we can't have a value starting with '0' unless its length is 1
184            if ( ( length > 1 ) && ( latest[0] == '0' ) )
185            {
186                throw new RuntimeException( "Overflow for " + valStr );
187            }
188            
189            String anonymizedValue = new String( latest );
190            
191            if ( overflow )
192            {
193                // We have exhausted all the possible values...
194                throw new RuntimeException( "Cannot compute a new value for " + anonymizedValue );
195            }
196            
197            latestIntegerMap.put( length, anonymizedValue );
198            
199            return anonymizedValue;
200        }
201    }
202}