blob: 1247dd0a8f2d916c0f39b597d6962acfcc573c1a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.directory.studio.ldapbrowser.core.utils;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.text.translate.CharSequenceTranslator;
import org.apache.commons.text.translate.LookupTranslator;
import org.apache.directory.api.ldap.model.name.Ava;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.api.ldap.model.url.LdapUrl;
import org.apache.directory.api.util.Strings;
import org.apache.directory.studio.connection.core.ConnectionParameter.EncryptionMethod;
import org.apache.directory.studio.connection.core.StudioControl;
import org.apache.directory.studio.ldapbrowser.core.BrowserCoreConstants;
import org.apache.directory.studio.ldapbrowser.core.BrowserCorePlugin;
import org.apache.directory.studio.ldapbrowser.core.model.IAttribute;
import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection;
import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection.ModifyMode;
import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection.ModifyOrder;
import org.apache.directory.studio.ldapbrowser.core.model.IEntry;
import org.apache.directory.studio.ldapbrowser.core.model.ISearch;
import org.apache.directory.studio.ldapbrowser.core.model.IValue;
import org.apache.directory.studio.ldapbrowser.core.model.schema.Schema;
import org.apache.directory.studio.ldapbrowser.core.model.schema.SchemaUtils;
import org.apache.directory.studio.ldifparser.LdifFormatParameters;
import org.apache.directory.studio.ldifparser.LdifUtils;
import org.apache.directory.studio.ldifparser.model.LdifFile;
import org.apache.directory.studio.ldifparser.model.container.LdifChangeModifyRecord;
import org.apache.directory.studio.ldifparser.model.container.LdifModSpec;
import org.apache.directory.studio.ldifparser.model.lines.LdifAttrValLine;
import org.apache.directory.studio.ldifparser.model.lines.LdifChangeTypeLine;
import org.apache.directory.studio.ldifparser.model.lines.LdifControlLine;
import org.apache.directory.studio.ldifparser.model.lines.LdifDnLine;
import org.apache.directory.studio.ldifparser.model.lines.LdifModSpecSepLine;
import org.apache.directory.studio.ldifparser.model.lines.LdifSepLine;
import org.eclipse.core.runtime.Preferences;
public class Utils
{
/**
* Transforms the given Dn into a normalized String, usable by the schema cache.
* The following transformations are performed:
* <ul>
* <li>The attribute type is replaced by the OID
* <li>The attribute value is trimmed and lowercased
* </ul>
* Example: the surname=Bar will be transformed to
* 2.5.4.4=bar
*
*
* @param dn the Dn
* @param schema the schema
*
* @return the oid string
*/
public static String getNormalizedOidString( Dn dn, Schema schema )
{
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for ( Rdn rdn : dn )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ',' );
}
sb.append( getOidString( rdn, schema ) );
}
return sb.toString();
}
private static String getOidString( Rdn rdn, Schema schema )
{
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for ( Ava ava : rdn )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( '+' );
}
sb.append( getOidString( ava, schema ) );
}
return sb.toString();
}
private static String getOidString( Ava ava, Schema schema )
{
String oid = schema != null ? schema.getAttributeTypeDescription( ava.getNormType() ).getOid() : ava
.getNormType();
return Strings.toLowerCaseAscii( Strings.trim( oid ) )
+ "=" + Strings.trim( ava.getValue().getString() ).toLowerCase(); //$NON-NLS-1$
}
public static String arrayToString( String[] array )
{
if ( array == null || array.length == 0 )
{
return ""; //$NON-NLS-1$
}
else
{
StringBuilder sb = new StringBuilder( array[0] );
for ( int i = 1; i < array.length; i++ )
{
sb.append( ", " ); //$NON-NLS-1$
sb.append( array[i] );
}
return sb.toString();
}
}
public static boolean equals( byte[] data1, byte[] data2 )
{
if ( data1 == data2 )
return true;
if ( data1 == null || data2 == null )
return false;
if ( data1.length != data2.length )
return false;
for ( int i = 0; i < data1.length; i++ )
{
if ( data1[i] != data2[i] )
return false;
}
return true;
}
public static String getShortenedString( String value, int length )
{
StringBuilder sb = new StringBuilder();
if ( ( value != null ) && ( value.length() > length ) )
{
sb.append( value.substring( 0, length ) ).append( "..." ); //$NON-NLS-1$
}
return sb.toString();
}
public static String serialize( Object o )
{
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader( Utils.class.getClassLoader() );
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XMLEncoder encoder = new XMLEncoder( baos );
encoder.writeObject( o );
encoder.close();
return LdifUtils.utf8decode( baos.toByteArray() );
}
finally
{
Thread.currentThread().setContextClassLoader( ccl );
}
}
public static Object deserialize( String s )
{
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader( Utils.class.getClassLoader() );
ByteArrayInputStream bais = new ByteArrayInputStream( LdifUtils.utf8encode( s ) );
XMLDecoder decoder = new XMLDecoder( bais );
Object o = decoder.readObject();
decoder.close();
return o;
}
finally
{
Thread.currentThread().setContextClassLoader( ccl );
}
}
public static String getNonNullString( Object o )
{
return o == null ? "-" : o.toString(); //$NON-NLS-1$
}
public static String formatBytes( long bytes )
{
String size = ""; //$NON-NLS-1$
if ( bytes > 1024 * 1024 )
{
size += ( bytes / 1024 / 1024 )
+ " " + Messages.Utils_MegaBytes + " (" + bytes + " " + Messages.Utils_Bytes + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-6$
}
else if ( bytes > 1024 )
{
size += ( bytes / 1024 ) + " " + Messages.Utils_KiloBytes + " (" + bytes + " " + Messages.Utils_Bytes + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-6$
}
else if ( bytes > 1 )
{
size += bytes + " " + Messages.Utils_Bytes; //$NON-NLS-1$
}
else
{
size += bytes + " " + Messages.Utils_Byte; //$NON-NLS-1$
}
return size;
}
public static boolean containsIgnoreCase( Collection<String> c, String s )
{
if ( c == null || s == null )
{
return false;
}
for ( String string : c )
{
if ( string.equalsIgnoreCase( s ) )
{
return true;
}
}
return false;
}
public static LdifFormatParameters getLdifFormatParameters()
{
Preferences store = BrowserCorePlugin.getDefault().getPluginPreferences();
boolean spaceAfterColon = store.getBoolean( BrowserCoreConstants.PREFERENCE_LDIF_SPACE_AFTER_COLON );
int lineWidth = store.getInt( BrowserCoreConstants.PREFERENCE_LDIF_LINE_WIDTH );
String lineSeparator = store.getString( BrowserCoreConstants.PREFERENCE_LDIF_LINE_SEPARATOR );
return new LdifFormatParameters( spaceAfterColon, lineWidth, lineSeparator );
}
/**
* Transforms an IBrowserConnection to an LdapURL. The following parameters are
* used to create the LDAP URL:
* <ul>
* <li>scheme
* <li>host
* <li>port
* </ul>
*
* @param entry the entry
* @return the LDAP URL
*/
public static LdapUrl getLdapURL( IBrowserConnection browserConnection )
{
LdapUrl url = new LdapUrl();
if ( browserConnection.getConnection() != null )
{
if ( browserConnection.getConnection().getEncryptionMethod() == EncryptionMethod.LDAPS )
{
url.setScheme( LdapUrl.LDAPS_SCHEME );
}
else
{
url.setScheme( LdapUrl.LDAP_SCHEME );
}
url.setHost( browserConnection.getConnection().getHost() );
url.setPort( browserConnection.getConnection().getPort() );
}
return url;
}
/**
* Transforms an IEntry to an LdapURL. The following parameters are
* used to create the LDAP URL:
* <ul>
* <li>scheme
* <li>host
* <li>port
* <li>dn
* </ul>
*
* @param entry the entry
* @return the LDAP URL
*/
public static LdapUrl getLdapURL( IEntry entry )
{
LdapUrl url = getLdapURL( entry.getBrowserConnection() );
url.setDn( entry.getDn() );
return url;
}
/**
* Transforms an ISearch to an LdapURL. The following search parameters are
* used to create the LDAP URL:
* <ul>
* <li>scheme
* <li>host
* <li>port
* <li>search base
* <li>returning attributes
* <li>scope
* <li>filter
* </ul>
*
* @param search the search
* @return the LDAP URL
*/
public static LdapUrl getLdapURL( ISearch search )
{
LdapUrl url = getLdapURL( search.getBrowserConnection() );
url.setDn( search.getSearchBase() );
if ( search.getReturningAttributes() != null )
{
url.setAttributes( Arrays.asList( search.getReturningAttributes() ) );
}
url.setScope( search.getScope().getScope() );
url.setFilter( search.getFilter() );
return url;
}
/**
* Computes the difference between the old and the new entry
* and returns an LDIF that could be applied to the old entry
* to get new entry.
*
* @param oldEntry the old entry
* @param newEntry the new entry
* @return the change modify record or null if there is no difference
* between the two entries
*/
public static LdifFile computeDiff( IEntry oldEntry, IEntry newEntry )
{
// get connection parameters
ModifyMode modifyMode = oldEntry.getBrowserConnection().getModifyMode();
ModifyMode modifyModeNoEMR = oldEntry.getBrowserConnection().getModifyModeNoEMR();
ModifyOrder modifyAddDeleteOrder = oldEntry.getBrowserConnection().getModifyAddDeleteOrder();
// get all attribute descriptions
Set<String> attributeDescriptions = new LinkedHashSet<>();
for ( IAttribute oldAttr : oldEntry.getAttributes() )
{
attributeDescriptions.add( oldAttr.getDescription() );
}
for ( IAttribute newAttr : newEntry.getAttributes() )
{
attributeDescriptions.add( newAttr.getDescription() );
}
// prepare the LDIF record containing the modifications
LdifChangeModifyRecord record = new LdifChangeModifyRecord( LdifDnLine.create( newEntry.getDn().getName() ) );
if ( newEntry.isReferral() )
{
record.addControl( LdifControlLine.create( StudioControl.MANAGEDSAIT_CONTROL.getOid(),
StudioControl.MANAGEDSAIT_CONTROL.isCritical(), StudioControl.MANAGEDSAIT_CONTROL.getControlValue() ) );
}
record.setChangeType( LdifChangeTypeLine.createModify() );
// check all the attributes
for ( String attributeDescription : attributeDescriptions )
{
// get attribute type schema information
Schema schema = oldEntry.getBrowserConnection().getSchema();
AttributeType atd = schema.getAttributeTypeDescription( attributeDescription );
boolean hasEMR = SchemaUtils.getEqualityMatchingRuleNameOrNumericOidTransitive( atd, schema ) != null;
boolean isReplaceForced = ( hasEMR && modifyMode == ModifyMode.REPLACE )
|| ( !hasEMR && modifyModeNoEMR == ModifyMode.REPLACE );
boolean isAddDelForced = ( hasEMR && modifyMode == ModifyMode.ADD_DELETE )
|| ( !hasEMR && modifyModeNoEMR == ModifyMode.ADD_DELETE );
boolean isOrderedValue = atd.getExtensions().containsKey( "X-ORDERED" ) //$NON-NLS-1$
&& atd.getExtensions().get( "X-ORDERED" ).contains( "VALUES" ); //$NON-NLS-1$ //$NON-NLS-2$
// get old an new values for comparison
IAttribute oldAttribute = oldEntry.getAttribute( attributeDescription );
Set<String> oldValues = new HashSet<>();
Map<String, LdifAttrValLine> oldAttrValLines = new LinkedHashMap<>();
if ( oldAttribute != null )
{
for ( IValue value : oldAttribute.getValues() )
{
LdifAttrValLine attrValLine = computeDiffCreateAttrValLine( value );
oldValues.add( attrValLine.getUnfoldedValue() );
oldAttrValLines.put( attrValLine.getUnfoldedValue(), attrValLine );
}
}
IAttribute newAttribute = newEntry.getAttribute( attributeDescription );
Set<String> newValues = new HashSet<>();
Map<String, LdifAttrValLine> newAttrValLines = new LinkedHashMap<>();
if ( newAttribute != null )
{
for ( IValue value : newAttribute.getValues() )
{
LdifAttrValLine attrValLine = computeDiffCreateAttrValLine( value );
newValues.add( attrValLine.getUnfoldedValue() );
newAttrValLines.put( attrValLine.getUnfoldedValue(), attrValLine );
}
}
// check what to do
if ( oldAttribute != null && newAttribute == null )
{
// attribute only exists in the old entry: delete all values
LdifModSpec modSpec;
if ( isReplaceForced )
{
// replace (empty value list)
modSpec = LdifModSpec.createReplace( attributeDescription );
}
else
// addDelForced or default
{
// delete all
modSpec = LdifModSpec.createDelete( attributeDescription );
for ( IValue value : oldAttribute.getValues() )
{
modSpec.addAttrVal( computeDiffCreateAttrValLine( value ) );
}
}
modSpec.finish( LdifModSpecSepLine.create() );
record.addModSpec( modSpec );
}
else if ( oldAttribute == null && newAttribute != null )
{
// attribute only exists in the new entry: add all values
LdifModSpec modSpec;
if ( isReplaceForced )
{
// replace (all values)
modSpec = LdifModSpec.createReplace( attributeDescription );
}
else
// addDelForced or default
{
// add (all new values)
modSpec = LdifModSpec.createAdd( attributeDescription );
}
for ( IValue value : newAttribute.getValues() )
{
modSpec.addAttrVal( computeDiffCreateAttrValLine( value ) );
}
modSpec.finish( LdifModSpecSepLine.create() );
record.addModSpec( modSpec );
}
else if ( oldAttribute != null && newAttribute != null && !oldValues.equals( newValues ) )
{
// attribute exists in both entries, check modifications
if ( isReplaceForced )
{
// replace (all new values)
LdifModSpec modSpec = LdifModSpec.createReplace( attributeDescription );
for ( IValue value : newAttribute.getValues() )
{
modSpec.addAttrVal( computeDiffCreateAttrValLine( value ) );
}
modSpec.finish( LdifModSpecSepLine.create() );
record.addModSpec( modSpec );
}
else
{
// compute diff
List<LdifAttrValLine> toDel = new ArrayList<>();
List<LdifAttrValLine> toAdd = new ArrayList<>();
for ( Map.Entry<String, LdifAttrValLine> entry : oldAttrValLines.entrySet() )
{
if ( !newValues.contains( entry.getKey() ) )
{
toDel.add( entry.getValue() );
}
}
for ( Map.Entry<String, LdifAttrValLine> entry : newAttrValLines.entrySet() )
{
if ( !oldValues.contains( entry.getKey() ) )
{
toAdd.add( entry.getValue() );
}
}
/*
* we use add/del in the following cases:
* - add/del is forced in the connection configuration
* - for attributes w/o X-ORDERED 'VALUES'
*
* we use replace in the following cases:
* - for attributes with X-ORDERED 'VALUES'
*/
if ( isAddDelForced || !isOrderedValue )
{
// add/del del/add
LdifModSpec addModSpec = LdifModSpec.createAdd( attributeDescription );
for ( LdifAttrValLine attrValLine : toAdd )
{
addModSpec.addAttrVal( attrValLine );
}
addModSpec.finish( LdifModSpecSepLine.create() );
LdifModSpec delModSpec = LdifModSpec.createDelete( attributeDescription );
for ( LdifAttrValLine attrValLine : toDel )
{
delModSpec.addAttrVal( attrValLine );
}
delModSpec.finish( LdifModSpecSepLine.create() );
if ( modifyAddDeleteOrder == ModifyOrder.DELETE_FIRST )
{
if ( delModSpec.getAttrVals().length > 0 )
{
record.addModSpec( delModSpec );
}
if ( addModSpec.getAttrVals().length > 0 )
{
record.addModSpec( addModSpec );
}
}
else
{
if ( addModSpec.getAttrVals().length > 0 )
{
record.addModSpec( addModSpec );
}
if ( delModSpec.getAttrVals().length > 0 )
{
record.addModSpec( delModSpec );
}
}
}
else
{
// replace (all new values)
LdifModSpec modSpec = LdifModSpec.createReplace( attributeDescription );
for ( LdifAttrValLine attrValLine : newAttrValLines.values() )
{
modSpec.addAttrVal( attrValLine );
}
modSpec.finish( LdifModSpecSepLine.create() );
record.addModSpec( modSpec );
}
}
}
}
record.finish( LdifSepLine.create() );
LdifFile model = new LdifFile();
if ( record.isValid() && record.getModSpecs().length > 0 )
{
model.addContainer( record );
}
return model.getRecords().length > 0 ? model : null;
}
private static LdifAttrValLine computeDiffCreateAttrValLine( IValue value )
{
IAttribute attribute = value.getAttribute();
if ( attribute.isBinary() )
{
return LdifAttrValLine.create( attribute.getDescription(), value.getBinaryValue() );
}
else
{
return LdifAttrValLine.create( attribute.getDescription(), value.getStringValue() );
}
}
/**
* Decodes the RFC 4517 Postal Address syntax.
*
* <pre>
* PostalAddress = line *( DOLLAR line )
* line = 1*line-char
* line-char = %x00-23
* / (%x5C "24") ; escaped "$"
* / %x25-5B
* / (%x5C "5C") ; escaped "\"
* / %x5D-7F
* / UTFMB
* </pre>
*
* @param separator the separator to output between address lines
* @return a translator object for decoding
*/
public static CharSequenceTranslator createPostalAddressDecoder( String separator )
{
return new LookupTranslator( Map.of(
"$", separator, //$NON-NLS-1$
"\\24", "$", //$NON-NLS-1$ //$NON-NLS-2$
"\\5C", "\\", //$NON-NLS-1$ //$NON-NLS-2$
"\\5c", "\\" ) ); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Encodes the RFC 4517 Postal Address syntax.
*
* @param separator the separator used between address lines
* @return a translator object for encoding
*/
public static CharSequenceTranslator createPostalAddressEncoder( String separator )
{
return new LookupTranslator( Map.of(
"\\", "\\5C", //$NON-NLS-1$ //$NON-NLS-2$
"$", "\\24", //$NON-NLS-1$ //$NON-NLS-2$
separator, "$" ) ); //$NON-NLS-1$
}
}