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.schema.registries.synchronizers;
021
022
023import java.util.ArrayList;
024import java.util.List;
025
026import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
027import org.apache.directory.api.ldap.model.constants.SchemaConstants;
028import org.apache.directory.api.ldap.model.entry.Entry;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
031import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
032import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
033import org.apache.directory.api.ldap.model.name.Dn;
034import org.apache.directory.api.ldap.model.name.Rdn;
035import org.apache.directory.api.ldap.model.schema.AttributeType;
036import org.apache.directory.api.ldap.model.schema.LdapSyntax;
037import org.apache.directory.api.ldap.model.schema.MatchingRule;
038import org.apache.directory.api.ldap.model.schema.SchemaManager;
039import org.apache.directory.api.ldap.model.schema.SchemaObject;
040import org.apache.directory.api.ldap.model.schema.registries.Schema;
041import org.apache.directory.api.util.Strings;
042import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
043import org.apache.directory.server.i18n.I18n;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047
048/**
049 * A syntax specific registry synchronizer which responds to syntax entry 
050 * changes in the DIT to update the syntax registry.
051 *
052 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
053 */
054public class SyntaxSynchronizer extends AbstractRegistrySynchronizer
055{
056    /** A logger for this class */
057    private static final Logger LOG = LoggerFactory.getLogger( SyntaxSynchronizer.class );
058
059
060    /**
061     * Creates a new instance of SyntaxSynchronizer.
062     *
063     * @param schemaManager The global schemaManager
064     * @throws Exception If the initialization failed
065     */
066    public SyntaxSynchronizer( SchemaManager schemaManager ) throws Exception
067    {
068        super( schemaManager );
069    }
070
071
072    /**
073     * {@inheritDoc}
074     */
075    @Override
076    public boolean modify( ModifyOperationContext modifyContext, Entry targetEntry, boolean cascade )
077        throws LdapException
078    {
079        Dn name = modifyContext.getDn();
080        Entry entry = modifyContext.getEntry();
081        String oid = getOid( entry );
082        LdapSyntax syntax = factory.getSyntax( schemaManager, targetEntry, schemaManager.getRegistries(),
083            getSchemaName( name ) );
084        String schemaName = getSchemaName( entry.getDn() );
085
086        if ( isSchemaEnabled( schemaName ) )
087        {
088            schemaManager.unregisterLdapSyntax( oid );
089            schemaManager.add( syntax );
090
091            return SCHEMA_MODIFIED;
092        }
093
094        return SCHEMA_UNCHANGED;
095    }
096
097
098    /**
099     * {@inheritDoc}
100     */
101    @Override
102    public void add( Entry entry ) throws LdapException
103    {
104        Dn dn = entry.getDn();
105        Dn parentDn = dn.getParent();
106
107        // The parent Dn must be ou=syntaxes,cn=<schemaName>,ou=schema
108        checkParent( parentDn, schemaManager, SchemaConstants.SYNTAX );
109
110        // The new schemaObject's OID must not already exist
111        checkOidIsUnique( entry );
112
113        // Build the new Syntax from the given entry
114        String schemaName = getSchemaName( dn );
115
116        LdapSyntax syntax = factory.getSyntax( schemaManager, entry, schemaManager.getRegistries(), schemaName );
117
118        // At this point, the constructed Syntax has not been checked against the 
119        // existing Registries. It may be broken (missing SUP, or such), it will be checked
120        // there, if the schema and the Syntax are both enabled.
121        Schema schema = schemaManager.getLoadedSchema( schemaName );
122
123        if ( schema.isEnabled() && syntax.isEnabled() )
124        {
125            if ( schemaManager.add( syntax ) )
126            {
127                LOG.debug( "Added {} into the enabled schema {}", dn.getName(), schemaName );
128            }
129            else
130            {
131                // We have some error : reject the addition and get out
132                String msg = I18n.err( I18n.ERR_399, entry.getDn().getName(),
133                    Strings.listToString( schemaManager.getErrors() ) );
134                LOG.info( msg );
135                throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg );
136            }
137        }
138        else
139        {
140            LOG.debug( "The Syntax {} cannot be added in the disabled schema {}", dn.getName(), schemaName );
141        }
142    }
143
144
145    /**
146     * Check if a syntax is used by an AT or a MR
147     */
148    private List<SchemaObject> checkInUse( String oid )
149    {
150        List<SchemaObject> dependees = new ArrayList<>();
151
152        for ( AttributeType attributeType : schemaManager.getAttributeTypeRegistry() )
153        {
154            if ( oid.equals( attributeType.getSyntax().getOid() ) )
155            {
156                dependees.add( attributeType );
157            }
158        }
159
160        for ( MatchingRule matchingRule : schemaManager.getMatchingRuleRegistry() )
161        {
162            if ( oid.equals( matchingRule.getSyntax().getOid() ) )
163            {
164                dependees.add( matchingRule );
165            }
166        }
167
168        return dependees;
169    }
170
171
172    /**
173     * Get the list of SchemaObject's name using a given syntax
174     */
175    private String getNames( List<SchemaObject> schemaObjects )
176    {
177        StringBuilder sb = new StringBuilder();
178        boolean isFirst = true;
179
180        for ( SchemaObject schemaObject : schemaObjects )
181        {
182            if ( isFirst )
183            {
184                isFirst = false;
185            }
186            else
187            {
188                sb.append( ", " );
189            }
190
191            sb.append( schemaObject.getName() );
192        }
193
194        return sb.toString();
195    }
196
197
198    /**
199     * {@inheritDoc}
200     */
201    @Override
202    public void delete( Entry entry, boolean cascade ) throws LdapException
203    {
204        Dn dn = entry.getDn();
205        Dn parentDn = dn.getParent();
206
207        // The parent Dn must be ou=syntaxes,cn=<schemaName>,ou=schema
208        checkParent( parentDn, schemaManager, SchemaConstants.SYNTAX );
209
210        // Get the Syntax from the given entry ( it has been grabbed from the server earlier)
211        String schemaName = getSchemaName( entry.getDn() );
212
213        // Get the schema 
214        Schema schema = schemaManager.getLoadedSchema( schemaName );
215
216        if ( schema.isDisabled() )
217        {
218            // The schema is disabled, nothing to do.
219            LOG.debug( "The Syntax {} cannot be removed from the disabled schema {}.",
220                dn.getName(), schemaName );
221
222            return;
223        }
224
225        // Test that the Oid exists
226        LdapSyntax syntax = ( LdapSyntax ) checkOidExists( entry );
227
228        List<Throwable> errors = new ArrayList<>();
229
230        if ( schema.isEnabled() && syntax.isEnabled() )
231        {
232            if ( schemaManager.delete( syntax ) )
233            {
234                LOG.debug( "Removed {} from the schema {}", syntax, schemaName );
235            }
236            else
237            {
238                // We have some error : reject the deletion and get out
239                String msg = I18n.err( I18n.ERR_400, entry.getDn().getName(),
240                    Strings.listToString( errors ) );
241                LOG.info( msg );
242                throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg );
243            }
244        }
245        else
246        {
247            LOG.debug( "Removed {} from the disabled schema {}", syntax, schemaName );
248        }
249    }
250
251
252    /**
253     * {@inheritDoc}
254     */
255    @Override
256    public void rename( Entry entry, Rdn newRdn, boolean cascade ) throws LdapException
257    {
258        String oldOid = getOid( entry );
259        String schemaName = getSchemaName( entry.getDn() );
260
261        // Check that this syntax is not used by an AttributeType
262        List<SchemaObject> dependees = checkInUse( oldOid );
263
264        if ( !dependees.isEmpty() )
265        {
266            throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, I18n.err( I18n.ERR_401,
267                oldOid,
268                getNames( dependees ) ) );
269        }
270
271        Entry targetEntry = entry.clone();
272        String newOid = newRdn.getValue();
273        checkOidIsUnique( newOid );
274
275        targetEntry.put( MetaSchemaConstants.M_OID_AT, newOid );
276        LdapSyntax syntax = factory.getSyntax( schemaManager, targetEntry, schemaManager.getRegistries(),
277            getSchemaName( entry.getDn() ) );
278
279        if ( isSchemaEnabled( schemaName ) )
280        {
281            schemaManager.unregisterLdapSyntax( oldOid );
282            schemaManager.add( syntax );
283        }
284        else
285        {
286            // always remove old OIDs that are not in schema anymore
287            unregisterOids( syntax );
288            // even for disabled schemas add OIDs
289            registerOids( syntax );
290        }
291    }
292
293
294    /**
295     * {@inheritDoc}
296     */
297    @Override
298    public void moveAndRename( Dn oriChildName, Dn newParentName, Rdn newRn, boolean deleteOldRn,
299        Entry entry, boolean cascade ) throws LdapException
300    {
301        checkNewParent( newParentName );
302        String oldOid = getOid( entry );
303        String oldSchemaName = getSchemaName( oriChildName );
304        String newSchemaName = getSchemaName( newParentName );
305
306        // Check that this syntax is not used by an AttributeType
307        List<SchemaObject> dependees = checkInUse( oldOid );
308
309        if ( !dependees.isEmpty() )
310        {
311            throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
312                I18n.err( I18n.ERR_401, oldOid, getNames( dependees ) ) );
313        }
314
315        Entry targetEntry = entry.clone();
316        String newOid = newRn.getValue();
317        checkOidIsUnique( newOid );
318
319        targetEntry.put( MetaSchemaConstants.M_OID_AT, newOid );
320        LdapSyntax syntax = factory.getSyntax( schemaManager, targetEntry, schemaManager.getRegistries(),
321            getSchemaName( newParentName ) );
322
323        if ( isSchemaEnabled( oldSchemaName ) )
324        {
325            schemaManager.unregisterLdapSyntax( oldOid );
326        }
327        else
328        {
329            unregisterOids( syntax );
330        }
331
332        if ( isSchemaEnabled( newSchemaName ) )
333        {
334            schemaManager.add( syntax );
335        }
336        else
337        {
338            // register new syntax OIDs even if schema is disabled 
339            registerOids( syntax );
340        }
341    }
342
343
344    /**
345     * {@inheritDoc}
346     */
347    @Override
348    public void move( Dn oriChildName, Dn newParentName, Entry entry, boolean cascade ) throws LdapException
349    {
350        checkNewParent( newParentName );
351        String oid = getOid( entry );
352        String oldSchemaName = getSchemaName( oriChildName );
353        String newSchemaName = getSchemaName( newParentName );
354
355        LdapSyntax syntax = factory.getSyntax( schemaManager, entry, schemaManager.getRegistries(),
356            getSchemaName( newParentName ) );
357
358        if ( isSchemaEnabled( oldSchemaName ) )
359        {
360            schemaManager.unregisterLdapSyntax( oid );
361        }
362        else
363        {
364            unregisterOids( syntax );
365        }
366
367        if ( isSchemaEnabled( newSchemaName ) )
368        {
369            schemaManager.add( syntax );
370        }
371        else
372        {
373            registerOids( syntax );
374        }
375    }
376
377
378    private void checkNewParent( Dn newParent ) throws LdapException
379    {
380        if ( newParent.size() != 3 )
381        {
382            throw new LdapInvalidDnException( ResultCodeEnum.NAMING_VIOLATION,
383                I18n.err( I18n.ERR_402 ) );
384        }
385
386        Rdn rdn = newParent.getRdn();
387        if ( !schemaManager.getAttributeTypeRegistry().getOidByName( rdn.getNormType() ).equals(
388            SchemaConstants.OU_AT_OID ) )
389        {
390            throw new LdapInvalidDnException( ResultCodeEnum.NAMING_VIOLATION, I18n.err( I18n.ERR_403 ) );
391        }
392
393        if ( !rdn.getValue().equalsIgnoreCase( SchemaConstants.SYNTAXES ) )
394        {
395            throw new LdapInvalidDnException( ResultCodeEnum.NAMING_VIOLATION, I18n.err( I18n.ERR_363 ) );
396        }
397    }
398}