mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

jdemendi
30.09.2007 403b6a83e7d68de2b5159c3421b8d91d704566bb
s set of files provides the workflow configuration manual mode.

Until now, the workflows were automatically configured-a wokflow
was created for each base DN in the backends. When new suffixes
were added or when a backend was added, the associated workflows
were also created (and simillarly workflows were deleted as suffixes
or backends were removed).

With the manual mode, each and every workflow in the server must
be defined explicitely in the configuration. By default, the server is
running in automatic configuration mode. To have a server running
with manual configuration mode one must set the attribute in
cn=config:

dn: cn=config
...
ds-cfg-workflow-configuration-mode: auto|manual


No attribute means "auto" mode.

The workflow configuration consist of 3 parts:
- the configuration of workfow elements
- the configuration of workfows
- the configuration of network groups


The Workflow Elements - A workflow element is a basic task in a
workflow. The workflow elements are organized in trees and the
simplest tree is made of one element. For example, the workflow
element that wraps a local backend is configured as follow:

dn: ds-cfg-workflow-element-id=userRoot,cn=workflow elements,cn=config
objectClass: top
objectClass: ds-cfg-workflow-element
objectClass: ds-cfg-local-backend-workflow-element
ds-cfg-workflow-element-id: userRoot
ds-cfg-enabled: true
ds-cfg-java-class: org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement
ds-cfg-backend: ds-cfg-backend-id=userRoot,cn=Backends,cn=config

From an admin standpoint, the local backend workflow element
is an aggregation of a single backend (attribute ds-cfg-backend).
So we cannot disable/delete a backend as long as it is used by a
local backend workflow element.


The Workflows - A workflow is a chain of processing and it's
targeting all the entries under a given baseDN. The processing
is actually identified by the root node of the task tree described
above. The configuration of a workflow looks like:

dn: ds-cfg-workflow-id=userRoot,cn=workflows,cn=config
objectClass: top
objectClass: ds-cfg-workflow
ds-cfg-workflow-id: userRoot
ds-cfg-enabled: true
ds-cfg-workflow-element: ds-cfg-workflow-element-id=userRoot,cn=workflow elements,cn=config
ds-cfg-base-dn: dc=example,dc=com

From an admin standpoint, the local workflow is an aggregation
of a single elements (attribute ds-cfg-workflow-element).
So we cannot disable/delete a workflow element as long as it is used
by a local workflow.


The Network Groups - A network group defines categories for
client connection. The network group contains a set of workflows
and each client operation is routed to one (or more) workflow(s).
By default, the server create a default network group which contains
all the workflows defined in the server. The default network group
looks like:

dn: ds-cfg-id=defaultNetworkGroup2,cn=network groups,cn=config
objectClass: top
objectClass: ds-cfg-network-group
ds-cfg-id: defaultNetworkGroup2
ds-cfg-enabled: true
ds-cfg-workflow: ds-cfg-workflow-id=adminRoot,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=ads-truststore,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=backup,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=config,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=monitor,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=schema,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=tasks,cn=Workflows,cn=config
ds-cfg-workflow: ds-cfg-workflow-id=userRoot,cn=Workflows,cn=config

From an admin standpoint, the network group is an aggregation
of several workflows (attribute ds-cfg-workflow). So we cannot
disable/delete a workflow as long as it is used by a network group.


A unit test named WorkflowConfigurationTest tests the configuration
of network groups, workflows and workflow elements.

8 files added
14 files modified
3261 ■■■■■ changed files
opends/resource/schema/02-config.ldif 66 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml 34 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/LocalBackendWorkflowElementConfiguration.xml 71 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/NetworkGroupConfiguration.xml 107 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml 41 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/WorkflowConfiguration.xml 132 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/WorkflowElementConfiguration.xml 111 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/config.properties 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CoreConfigManager.java 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 390 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/NetworkGroup.java 115 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/NetworkGroupConfigManager.java 305 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowConfigManager.java 313 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowImpl.java 61 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/WorkflowTopologyNode.java 17 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java 27 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/WorkflowElement.java 142 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/WorkflowElementConfigManager.java 448 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 142 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/ValidateConfigDefinitionsTest.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowConfigurationTest.java 689 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif
@@ -2133,6 +2133,40 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.435
  NAME 'ds-cfg-network-group-id'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.436
  NAME 'ds-cfg-workflow-id'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.437
  NAME 'ds-cfg-workflow'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.438
  NAME 'ds-cfg-workflow-element-id'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.439
  NAME 'ds-cfg-workflow-element'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.440
  NAME 'ds-cfg-workflow-configuration-mode'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.441
  NAME 'ds-cfg-backend'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -2531,6 +2565,7 @@
        ds-cfg-disabled-privilege $
        ds-cfg-return-bind-error-messages $
        ds-cfg-idle-time-limit $
        ds-cfg-workflow-configuration-mode $
        ds-cfg-save-config-on-successful-startup )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.40
@@ -3599,3 +3634,34 @@
  ds-cfg-key-length-bits $ ds-cfg-symmetric-key )
  MAY ds-cfg-key-compromised-time
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.176
  NAME 'ds-cfg-network-group'
  SUP top
  STRUCTURAL
  MUST ( ds-cfg-network-group-id $
         ds-cfg-enabled $
         ds-cfg-workflow )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.177
  NAME 'ds-cfg-workflow'
  SUP top
  STRUCTURAL
  MUST ( ds-cfg-workflow-id $
         ds-cfg-enabled $
         ds-cfg-workflow-element $
         ds-cfg-base-dn )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.178
  NAME 'ds-cfg-workflow-element'
  SUP top
  STRUCTURAL
  MUST ( ds-cfg-workflow-element-id $
         ds-cfg-enabled $
         ds-cfg-java-class )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.179
  NAME 'ds-cfg-local-backend-workflow-element'
  SUP ds-cfg-workflow-element
  STRUCTURAL
  MUST ( ds-cfg-backend )
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
@@ -725,5 +725,39 @@
    </adm:profile>
  </adm:property>
  <adm:property name="workflow-configuration-mode">
    <adm:synopsis>
      Specifies the workflow configuration mode (auto vs. manual).
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>auto</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="auto">
          <adm:synopsis>
             In the "auto" configuration mode there is no workflow
             configuration. The workflows are created automatically
             based on the backend configuration. There will be one
             workflow per backend base DN.
          </adm:synopsis>
        </adm:value>
        <adm:value name="manual">
          <adm:synopsis>
             In the "manual" configuration mode each workflow is created
             according to its description in the configuration.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-workflow-configuration-mode</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/LocalBackendWorkflowElementConfiguration.xml
New file
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
 ! CDDL HEADER START
 !
 ! The contents of this file are subject to the terms of the
 ! Common Development and Distribution License, Version 1.0 only
 ! (the "License").  You may not use this file except in compliance
 ! with the License.
 !
 ! You can obtain a copy of the license at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
 ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 ! See the License for the specific language governing permissions
 ! and limitations under the License.
 !
 ! When distributing Covered Code, include this CDDL HEADER in each
 ! file and include the License file at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 ! add the following below this CDDL HEADER, with the fields enclosed
 ! by brackets "[]" replaced with your own identifying information:
 !      Portions Copyright [yyyy] [name of copyright owner]
 !
 ! CDDL HEADER END
 !
 !
 !      Portions Copyright 2007 Sun Microsystems, Inc.
 ! -->
<adm:managed-object
  name="local-backend-workflow-element"
  plural-name="local-backend-workflow-elements"
  package="org.opends.server.admin.std"
  extends="workflow-element"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The <adm:user-friendly-name /> provides access to a local backend.
  </adm:synopsis>
  <adm:tag name="user-management"/>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-local-backend-workflow-element</ldap:name>
      <ldap:superior>ds-cfg-workflow-element</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="backend"
    mandatory="true"
    read-only="true"
    multi-valued="false">
    <adm:synopsis>
      Identifies the backend accessed by the workflow element.
    </adm:synopsis>
    <adm:syntax>
      <adm:aggregation relation-name="backend" parent-path="/">
        <adm:target-is-enabled-condition>
          <adm:contains property="enabled" value="true" />
        </adm:target-is-enabled-condition>
      </adm:aggregation>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-backend</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/NetworkGroupConfiguration.xml
New file
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
 ! CDDL HEADER START
 !
 ! The contents of this file are subject to the terms of the
 ! Common Development and Distribution License, Version 1.0 only
 ! (the "License").  You may not use this file except in compliance
 ! with the License.
 !
 ! You can obtain a copy of the license at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
 ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 ! See the License for the specific language governing permissions
 ! and limitations under the License.
 !
 ! When distributing Covered Code, include this CDDL HEADER in each
 ! file and include the License file at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 ! add the following below this CDDL HEADER, with the fields enclosed
 ! by brackets "[]" replaced with your own identifying information:
 !      Portions Copyright [yyyy] [name of copyright owner]
 !
 ! CDDL HEADER END
 !
 !
 !      Portions Copyright 2007 Sun Microsystems, Inc.
 ! -->
<adm:managed-object
  name="network-group"
  plural-name="network-groups"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The <adm:user-friendly-name /> is used to classify incoming connections.
  </adm:synopsis>
  <adm:tag name="user-management"/>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-network-group</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="enabled"
    mandatory="true"
    multi-valued="false">
    <adm:synopsis>
      Indicates whether the <adm:user-friendly-name />
      is enabled for use in the server.
    </adm:synopsis>
    <adm:description>
      If a network group is not enabled, then its contents will not be
      accessible when processing operations.
    </adm:description>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="network-group-id" mandatory="true" read-only="true"
    multi-valued="false">
    <adm:synopsis>
      Provides a name that will be used to identify the associated
      <adm:user-friendly-name />.
    </adm:synopsis>
    <adm:description>
      The name must be unique among all <adm:user-friendly-name />
      in the server.
    </adm:description>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-network-group-id</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="workflow" mandatory="true" read-only="true"
    multi-valued="true">
    <adm:synopsis>
      Identifies a workflow in the network group.
    </adm:synopsis>
    <adm:syntax>
      <adm:aggregation relation-name="workflow" parent-path="/">
        <adm:target-is-enabled-condition>
          <adm:contains property="enabled" value="true" />
        </adm:target-is-enabled-condition>
      </adm:aggregation>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-workflow</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
@@ -422,6 +422,47 @@
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:relation name="network-group">
    <adm:one-to-many naming-property="network-group-id"/>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Network Groups,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
    <adm:profile name="cli">
      <cli:relation>
        <cli:default-property name="enabled" />
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:relation name="workflow">
    <adm:one-to-many naming-property="workflow-id"/>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Workflows,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
    <adm:profile name="cli">
      <cli:relation>
        <cli:default-property name="enabled" />
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:relation name="workflow-element">
    <adm:one-to-many naming-property="workflow-element-id"/>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>
        cn=Workflow elements,cn=config
      </ldap:rdn-sequence>
    </adm:profile>
    <adm:profile name="cli">
      <cli:relation>
        <cli:default-property name="enabled" />
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:product-name>OpenDS Directory Server</adm:product-name>
  <adm:tag-definition name="logging">
    <adm:synopsis>Logging</adm:synopsis>
opends/src/admin/defn/org/opends/server/admin/std/WorkflowConfiguration.xml
New file
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
 ! CDDL HEADER START
 !
 ! The contents of this file are subject to the terms of the
 ! Common Development and Distribution License, Version 1.0 only
 ! (the "License").  You may not use this file except in compliance
 ! with the License.
 !
 ! You can obtain a copy of the license at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
 ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 ! See the License for the specific language governing permissions
 ! and limitations under the License.
 !
 ! When distributing Covered Code, include this CDDL HEADER in each
 ! file and include the License file at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 ! add the following below this CDDL HEADER, with the fields enclosed
 ! by brackets "[]" replaced with your own identifying information:
 !      Portions Copyright [yyyy] [name of copyright owner]
 !
 ! CDDL HEADER END
 !
 !
 !      Portions Copyright 2007 Sun Microsystems, Inc.
 ! -->
<adm:managed-object
  name="workflow"
  plural-name="workflows"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The <adm:user-friendly-name /> is list of tasks applied on a DIT.
  </adm:synopsis>
  <adm:tag name="user-management"/>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-workflow</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="enabled"
    mandatory="true"
    read-only="false"
    multi-valued="false">
    <adm:synopsis>
      Indicates whether the <adm:user-friendly-name />
      is enabled for use in the server.
    </adm:synopsis>
    <adm:description>
      If a workflow is not enabled, then its contents will not be
      accessible when processing operations.
    </adm:description>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="workflow-id"
    mandatory="true"
    read-only="true"
    multi-valued="false">
    <adm:synopsis>
      Provides a name that will be used to identify the associated
      <adm:user-friendly-name />.
    </adm:synopsis>
    <adm:description>
      The name must be unique among all <adm:user-friendly-name />
      in the server.
    </adm:description>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-workflow-id</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="workflow-element"
    mandatory="true"
    read-only="false"
    multi-valued="false">
    <adm:synopsis>
      The <adm:user-friendly-name /> identifies the root task of the worklfow.
    </adm:synopsis>
    <adm:description>
      All the tasks in the worklfow are organized in a tree. The root element
      of the tree is identified by the <adm:user-friendly-name />.
    </adm:description>
    <adm:syntax>
      <adm:aggregation relation-name="workflow-element" parent-path="/">
        <adm:target-is-enabled-condition>
          <adm:contains property="enabled" value="true" />
        </adm:target-is-enabled-condition>
      </adm:aggregation>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-workflow-element</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="base-dn"
    mandatory="true"
    read-only="false"
    multi-valued="false">
    <adm:synopsis>
      The <adm:user-friendly-name /> specifies the base DN of the data
      targeted by the worlflow.
    </adm:synopsis>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-base-dn</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property></adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/WorkflowElementConfiguration.xml
New file
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
 ! CDDL HEADER START
 !
 ! The contents of this file are subject to the terms of the
 ! Common Development and Distribution License, Version 1.0 only
 ! (the "License").  You may not use this file except in compliance
 ! with the License.
 !
 ! You can obtain a copy of the license at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
 ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 ! See the License for the specific language governing permissions
 ! and limitations under the License.
 !
 ! When distributing Covered Code, include this CDDL HEADER in each
 ! file and include the License file at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 ! add the following below this CDDL HEADER, with the fields enclosed
 ! by brackets "[]" replaced with your own identifying information:
 !      Portions Copyright [yyyy] [name of copyright owner]
 !
 ! CDDL HEADER END
 !
 !
 !      Portions Copyright 2007 Sun Microsystems, Inc.
 ! -->
<adm:managed-object
  name="workflow-element"
  plural-name="workflow-elements"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The <adm:user-friendly-name /> is a task part of a worklfow.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-workflow-element</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="enabled"
    mandatory="true"
    read-only="false"
    multi-valued="false">
    <adm:synopsis>
      Indicates whether the <adm:user-friendly-name />
      is enabled for use in the server.
    </adm:synopsis>
    <adm:description>
      If a workflow element is not enabled, then its contents will not be
      accessible when processing operations.
    </adm:description>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-enabled</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="workflow-element-id"
    mandatory="true"
    read-only="true"
    multi-valued="false">
    <adm:synopsis>
      Provides a name that will be used to identify the associated
      <adm:user-friendly-name />.
    </adm:synopsis>
    <adm:description>
      The name must be unique among all <adm:user-friendly-name />
      in the server.
    </adm:description>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-workflow-element-id</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="java-class" mandatory="true">
    <adm:synopsis>
      The fully-qualified name of the Java class that provides the
      <adm:user-friendly-name />
      implementation.
    </adm:synopsis>
    <adm:syntax>
      <adm:java-class>
        <adm:instance-of>
          org.opends.server.workflowelement.WorkflowElement
        </adm:instance-of>
      </adm:java-class>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-java-class</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/messages/messages/config.properties
@@ -2109,3 +2109,23 @@
 %s will not take effect until the component for which it is set is restarted
SEVERE_ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE_709=An error occurred while \
 attempting to open the configured log file %s for logger %s:  %s
SEVERE_ERR_CONFIG_WORKFLOW_ELEMENT_CONFIG_NOT_ACCEPTABLE_710=The configuration \
 for the workflow element defined in configuration entry %s was not \
 acceptable: %s
SEVERE_ERR_CONFIG_WORKFLOW_ELEMENT_CANNOT_INITIALIZE_711=An error occurred \
 while trying to initialize a workflow element from class %s with the \
 information in configuration entry %s:  %s.  This workflow element will be \
 disabled
MILD_ERR_CONFIG_WORKFLOW_ELEMENT_ALREADY_REGISTERED_712=The workflow \
 element %s is already registered with the Directory Server. This workflow \
 element will be ignored
SEVERE_ERR_CONFIG_WORKFLOW_CANNOT_CONFIGURE_MANUAL_713=An error occurred \
 while trying to configure in manual mode the workflows in the \
 Directory Server, and rollback to automatic configuration mode has failed \
 too. If the server is in an unstable state restart it with the last \
 valid configuration
SEVERE_ERR_CONFIG_WORKFLOW_CANNOT_CONFIGURE_AUTO_714=An error occurred \
 while trying to configure in automatic mode the workflows in the \
 Directory Server, and rollback to manual configuration mode has failed \
 too. If the server is in an unstable state restart it with the last \
 valid configuration
opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -37,6 +37,7 @@
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.GlobalCfgDefn;
import org.opends.server.admin.std.meta.GlobalCfgDefn.WorkflowConfigurationMode;
import org.opends.server.admin.std.server.GlobalCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.admin.server.ServerManagementContext;
@@ -338,8 +339,25 @@
    DirectoryServer.setSaveConfigOnSuccessfulStartup(
         globalConfig.isSaveConfigOnSuccessfulStartup());
  }
    // If the workflow configuration mode has changed then reconfigure
    // the workflows-only if the server is running. If the server is not
    // running (ie. the server is starting up) simply update the workflow
    // configuration mode as the workflow configuration is processed
    // elsewhere.
    WorkflowConfigurationMode oldMode =
      DirectoryServer.getWorkflowConfigurationMode();
    WorkflowConfigurationMode newMode =
      globalConfig.getWorkflowConfigurationMode();
    if (DirectoryServer.isRunning())
    {
      DirectoryServer.reconfigureWorkflows(oldMode, newMode);
    }
    else
    {
      DirectoryServer.setWorkflowConfigurationMode(newMode);
    }
  }
  /**
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -30,6 +30,7 @@
import org.opends.server.admin.ClassLoaderProvider;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.meta.GlobalCfgDefn.WorkflowConfigurationMode;
import org.opends.server.admin.std.server.*;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.AlertGenerator;
@@ -191,6 +192,7 @@
import org.opends.server.protocols.internal.InternalConnectionHandler;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.crypto.CryptoManagerSync;
import static org.opends.messages.ConfigMessages.*;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
@@ -707,6 +709,24 @@
  // The writability mode for the Directory Server.
  private WritabilityMode writabilityMode;
  // The workflow configuration mode (auto or manual).
  private WorkflowConfigurationMode workflowConfigurationMode;
  // The network group config manager for the Directory Server.
  // This config manager is used when the workflow configuration
  // mode is 'manual'.
  private NetworkGroupConfigManager networkGroupConfigManager;
  // The workflow config manager for the Directory Server.
  // This config manager is used when the workflow configuration
  // mode is 'manual'.
  private WorkflowConfigManager workflowConfigManager;
  // The workflow element config manager for the Directory Server.
  // This config manager is used when the workflow configuration
  // mode is 'manual'.
  private WorkflowElementConfigManager workflowElementConfigManager;
  /**
@@ -1346,14 +1366,22 @@
      // Initialize the access control handler.
      AccessControlConfigManager.getInstance().initializeAccessControl();
      // Initialize all the backends and their associated suffixes.
      // Initialize all the backends and their associated suffixes
      // and initialize the workflows when workflow configuration mode
      // is auto.
      initializeBackends();
      // A first set of workflows had been created in the registerBackend
      // method. We now need to complete the workflow creation for the
      // backends that were not registered through the registerBackend
      // method (ie. cn=config and RootDSE).
      createAndRegisterRemainingWorkflows();
      // When workflow configuration mode is manual, do configure the
      // workflows now, else just configure the remaining workflows
      // (rootDSE and config backend).
      if (workflowConfigurationModeIsAuto())
      {
        createAndRegisterRemainingWorkflows();
      }
      else
      {
        configureWorkflowsManual();
      }
      // Check for and initialize user configured entry cache if any,
      // if not stick with default entry cache initialized earlier.
@@ -2582,51 +2610,32 @@
  /**
   * Deregisters a set of workflows each of which is identified with
   * a baseDN.
   *
   * In the first implementation, workflows are stored in the default network
   * group only.
   *
   * @param baseDNs  the DNs of the workflows to deregister
   */
  private static void deregisterWorkflows(
      DN[] baseDNs
      )
  {
    for (DN baseDN: baseDNs)
    {
      deregisterWorkflow(baseDN);
    }
  }
  /**
   * Deregisters one workflow with the appropriate network group.
   *
   * In the first implementation, workflows are stored in the default network
   * group only.
   * Deregisters a workflow with the default network group and
   * deregisters the workflow with the server. This method is
   * intended to be called when workflow configuration mode is
   * auto.
   *
   * @param baseDN  the DN of the workflow to deregister
   */
  private static void deregisterWorkflow(
  private static void deregisterWorkflowWithDefaultNetworkGroup(
      DN baseDN
      )
  {
    // Get the default network group and deregister all the workflows
    // being configured for the backend (reminder: there is one worklfow
    // per base DN configured in the backend).
    // being configured for the backend (there is one worklfow per
    // backend base DN).
    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
    defaultNetworkGroup.deregisterWorkflow (baseDN);
    Workflow workflow = defaultNetworkGroup.deregisterWorkflow(baseDN);
    WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
    workflowImpl.deregister();
  }
  /**
   * Creates a set of workflows for a given backend. There are as many
   * workflows as base DNs defined in the backend. Each workflow is
   * registered with the appropriate network group.
   *
   * TODO implement the registration with the appropriate network group.
   * Creates a set of workflows for a given backend and registers the
   * workflows with the default network group. There are as many workflows
   * as base DNs defined in the backend. This method is intended
   * to be called when workflow configuration mode is auto.
   *
   * @param backend  the backend handled by the workflow
   *
@@ -2634,35 +2643,33 @@
   *                              workflow conflicts with the workflow
   *                              ID of an existing workflow.
   */
  public static void createAndRegisterWorkflows(
  public static void createAndRegisterWorkflowsWithDefaultNetworkGroup(
      Backend backend
      ) throws DirectoryException
  {
    // Create a worklfow for each baseDN being configured
    // in the backend and register the workflow with the network groups.
    // In the automatic configuration mode, the workflow identifier is
    // set to the backend ID.
    // Create a worklfow for each backend base DN and register the workflow
    // with the default network group.
    for (DN curBaseDN: backend.getBaseDNs())
    {
      createAndRegisterWorkflow(curBaseDN, backend);
      WorkflowImpl workflowImpl = createWorkflow(curBaseDN, backend);
      registerWorkflowWithDefaultNetworkGroup(workflowImpl);
    }
  }
  /**
   * Creates one workflow for a given base DN in a backend. The workflow
   * is registered with the appropriate network group.
   *
   * TODO implement the registration with the appropriate network group.
   * Creates one workflow for a given base DN in a backend.
   *
   * @param baseDN   the base DN of the workflow to create
   * @param backend  the backend handled by the workflow
   *
   * @return the newly created workflow
   *
   * @throws  DirectoryException  If the workflow ID for the provided
   *                              workflow conflicts with the workflow
   *                              ID of an existing workflow.
   */
  public static void createAndRegisterWorkflow(
  public static WorkflowImpl createWorkflow(
      DN      baseDN,
      Backend backend
      ) throws DirectoryException
@@ -2671,49 +2678,52 @@
    // Create a root workflow element to encapsulate the backend
    LocalBackendWorkflowElement rootWE =
        LocalBackendWorkflowElement.create(backendID, backend);
        LocalBackendWorkflowElement.createAndRegister(backendID, backend);
    // The workflow ID is "backendID + baseDN".
    // We cannot use backendID as workflow identifier because a backend
    // may handle several base DNs. We cannot use baseDN either because
    // we might want to configure several workflows handling the same
    // baseDN through different network groups. So a mix of both
    // backendID and baseDN should be ok.
    String workflowID = backend.getBackendID() + "#" + baseDN.toString();
    // Create the worklfow for the base DN and register the workflow with
    // the appropriate network groups.
    // the server.
    WorkflowImpl workflowImpl = new WorkflowImpl(
        baseDN.toString(), baseDN, (WorkflowElement) rootWE);
    registerWorkflowInNetworkGroups(workflowImpl);
        workflowID, baseDN, (WorkflowElement) rootWE);
    workflowImpl.register();
    return workflowImpl;
  }
  /**
   * Registers a workflow with the appropriate network groups.
   * Registers a workflow with the default network group. This method
   * is intended to be called when workflow configuration mode is auto.
   *
   * In the first implementation, the workflow is registered with the
   * default network group only.
   * @param workflowImpl  The workflow to register with the
   *                      default network group
   *
   * TODO implement the registration with the appropriate network group.
   *
   * @param workflowImpl  the workflow to register
   *
   * @throws  DirectoryException  If the workflow ID for the provided
   *                              workflow conflicts with the workflow
   *                              ID of an existing workflow in a
   *                              network group.
   * @throws  DirectoryException  If the workflow is already registered with
   *                              the default network group
   */
  private static void registerWorkflowInNetworkGroups(
  private static void registerWorkflowWithDefaultNetworkGroup(
      WorkflowImpl workflowImpl
      ) throws DirectoryException
  {
    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
    defaultNetworkGroup.registerWorkflow(workflowImpl);
    // Now for each network group that exposes the baseDN of the workflow
    // create an instance of the workflow and register it with the network
    // group.
    // TODO jdemendi - we need the network group configuration to configure
    // the workflows per network group.
  }
  /**
   * Creates the workflows for the backends whose baseDNs were not registered
   * with registerBaseDN method, namely config backend and RootDSE backend.
   * Creates the missing workflows, one for the config backend and one for
   * the rootDSE backend.
   *
   * This method should be invoked whatever may be the workflow
   * configuration mode because config backend and rootDSE backend
   * will not have any configuration section, ever.
   *
   * @throws  ConfigException  If there is a configuration problem with any of
   *                           the workflows.
@@ -2723,8 +2733,8 @@
  {
    try
    {
      createAndRegisterWorkflows (configHandler);
      createAndRegisterWorkflows (rootDSEBackend);
      createAndRegisterWorkflowsWithDefaultNetworkGroup (configHandler);
      createAndRegisterWorkflowsWithDefaultNetworkGroup (rootDSEBackend);
    }
    catch (DirectoryException de)
    {
@@ -2734,6 +2744,151 @@
  /**
   * Reconfigures the workflows when configuration mode has changed.
   * This method is invoked when workflows need to be reconfigured
   * while the server is running. If the reconfiguration is valid
   * then the method update the workflow configuration mode.
   *
   * @param oldMode  the current workflow configuration mode
   * @param newMode  the new workflow configuration mode
   */
  public static void reconfigureWorkflows(
      WorkflowConfigurationMode oldMode,
      WorkflowConfigurationMode newMode)
  {
    if ((oldMode == WorkflowConfigurationMode.AUTO)
        && (newMode == WorkflowConfigurationMode.MANUAL))
    {
      // move to manual mode
      try
      {
        directoryServer.configureWorkflowsManual();
        setWorkflowConfigurationMode(newMode);
      }
      catch (Exception e)
      {
        // rollback to auto mode
        try
        {
           directoryServer.configureWorkflowsAuto();
        }
        catch (Exception ee)
        {
          // rollback to auto mode is failing too!!
          // well, just log an error message and suggest the admin
          // to restart the server with the last valid config...
          Message message = ERR_CONFIG_WORKFLOW_CANNOT_CONFIGURE_MANUAL.get();
          logError(message);
        }
      }
    }
    else if ((oldMode == WorkflowConfigurationMode.MANUAL)
        && (newMode == WorkflowConfigurationMode.AUTO))
    {
      // move to auto mode
      try
      {
        directoryServer.configureWorkflowsAuto();
        setWorkflowConfigurationMode(newMode);
      }
      catch (Exception e)
      {
        // rollback to manual mode
        try
        {
           directoryServer.configureWorkflowsManual();
        }
        catch (Exception ee)
        {
          // rollback to auto mode is failing too!!
          // well, just log an error message and suggest the admin
          // to restart the server with the last valid config...
          Message message = ERR_CONFIG_WORKFLOW_CANNOT_CONFIGURE_AUTO.get();
          logError(message);
        }
      }
    }
  }
  /**
   * Configures the workflows when configuration mode is manual.
   *
   * @throws  ConfigException  If there is a problem with the Directory Server
   *                           configuration that prevents a critical component
   *                           from being instantiated.
   *
   * @throws  InitializationException  If some other problem occurs while
   *                                   attempting to initialize and start the
   *                                   Directory Server.
   */
  private void configureWorkflowsManual()
      throws ConfigException, InitializationException
  {
    // First of all re-initialize the current workflow configuration
    NetworkGroup.resetConfig();
    WorkflowImpl.resetConfig();
    WorkflowElement.resetConfig();
    // Then configure the workflows
    workflowElementConfigManager = new WorkflowElementConfigManager();
    workflowElementConfigManager.initializeWorkflowElements();
    workflowConfigManager = new WorkflowConfigManager();
    workflowConfigManager.initializeWorkflows();
    networkGroupConfigManager = new NetworkGroupConfigManager();
    networkGroupConfigManager.initializeNetworkGroups();
    // We now need to complete the workflow creation for the
    // config backend and rootDSE backend.
    createAndRegisterRemainingWorkflows();
  }
  /**
   * Configures the workflows when configuration mode is auto.
   *
   * @throws  ConfigException  If there is a problem with the Directory Server
   *                           configuration that prevents a critical component
   *                           from being instantiated.
   */
  private void configureWorkflowsAuto() throws ConfigException
  {
    // First of all re-initialize the current workflow configuration
    NetworkGroup.resetConfig();
    WorkflowImpl.resetConfig();
    WorkflowElement.resetConfig();
    // For each base DN in a backend create a workflow and register
    // the workflow with the default network group
    Map<String, Backend> backends = getBackends();
    for (String backendID: backends.keySet())
    {
      Backend backend = backends.get(backendID);
      for (DN baseDN: backend.getBaseDNs())
      {
        WorkflowImpl workflowImpl;
        try
        {
          workflowImpl = createWorkflow(baseDN, backend);
          registerWorkflowWithDefaultNetworkGroup(workflowImpl);
        }
        catch (DirectoryException e)
        {
          // TODO Auto-generated catch block
          throw new ConfigException(e.getMessageObject());
        }
      }
    }
    // We now need to complete the workflow creation for the
    // config backend and rootDSE backend.
    createAndRegisterRemainingWorkflows();
  }
  /**
   * Initializes the Directory Server group manager.
   *
   * @throws  ConfigException  If there is a configuration problem with any of
@@ -6246,8 +6401,14 @@
      directoryServer.backends = newBackends;
      // Don't need anymore the local backend workflow element
      LocalBackendWorkflowElement.remove(backend.getBackendID());
      // Don't need anymore the local backend workflow element so we
      // can remove it. We do remove the workflow element only when
      // the workflow configuration mode is auto because in manual
      // mode the config manager is doing the job.
      if (workflowConfigurationModeIsAuto())
      {
        LocalBackendWorkflowElement.remove(backend.getBackendID());
      }
      BackendMonitor monitor = backend.getBackendMonitor();
@@ -6409,13 +6570,21 @@
        }
      }
      // Now create a workflow for the registered baseDN and register
      // the workflow with the network groups, but don't register the
      // workflow if the backend happens to be the configuration backend
      // because it's too soon.
      if (! baseDN.equals(DN.decode("cn=config")))
      // When a new baseDN is registered with the server we have to create
      // a new workflow to handle the base DN. We do not need to create
      // the workflow in manual mode because in that case the workflows
      // are created explicitely.
      if (workflowConfigurationModeIsAuto())
      {
        createAndRegisterWorkflow(baseDN, backend);
        // Now create a workflow for the registered baseDN and register
        // the workflow with the default network group, but don't register
        // the workflow if the backend happens to be the configuration
        // backend because it's too soon for the config backend.
        if (! baseDN.equals(DN.decode("cn=config")))
        {
          WorkflowImpl workflowImpl = createWorkflow(baseDN, backend);
          registerWorkflowWithDefaultNetworkGroup(workflowImpl);
        }
      }
    }
  }
@@ -6449,8 +6618,14 @@
        }
      }
      // Now deregister the workflow that was associated with the base DN.
      deregisterWorkflow(baseDN);
      // Now we need to deregister the workflow that was associated with
      // the base DN but we can do it only when the workflow configuration
      // mode is auto, because in manual mode the deregistration is done
      // by the workflow config manager.
      if (workflowConfigurationModeIsAuto())
      {
        deregisterWorkflowWithDefaultNetworkGroup(baseDN);
      }
    }
  }
@@ -9610,5 +9785,48 @@
    }
    return isRunningAsWindowsService;
  }
  /**
   * Specifies whether the workflows are configured automatically or manually.
   * In auto configuration mode one workflow is created for each and every
   * base DN in the local backends. In the auto configuration mode the
   * workflows are created according to their description in the configuration
   * file.
   *
   * @param  workflowConfigurationMode  Indicates whether the workflows are
   *                                    configured automatically or manually
   */
  public static void setWorkflowConfigurationMode(
      WorkflowConfigurationMode workflowConfigurationMode)
  {
    directoryServer.workflowConfigurationMode = workflowConfigurationMode;
  }
  /**
   * Indicates whether the workflow configuration mode is 'auto' or not.
   *
   * @return the workflow configuration mode
   */
  public static boolean workflowConfigurationModeIsAuto()
  {
    boolean isAuto =
      (directoryServer.workflowConfigurationMode
       == WorkflowConfigurationMode.AUTO);
    return isAuto;
  }
  /**
   * Retrieves the workflow configuration mode.
   *
   * @return the workflow configuration mode
   */
  public static WorkflowConfigurationMode getWorkflowConfigurationMode()
  {
    return directoryServer.workflowConfigurationMode;
  }
}
opends/src/server/org/opends/server/core/NetworkGroup.java
@@ -56,7 +56,7 @@
  // A lock to protect concurrent access to the registered Workflow nodes.
  private static Object registeredWorkflowNodesLock = new Object();
  private Object registeredWorkflowNodesLock = new Object();
  // The workflow node for the rootDSE entry. The RootDSE workflow node
@@ -107,6 +107,17 @@
  /**
   * Performs any finalization that might be required when this
   * network group is unloaded.  No action is taken in the
   * default implementation.
   */
  public void finalizeNetworkGroup()
  {
    // No action is required by default.
  }
  /**
   * Registers the current network group (this) with the server.
   *
   * @throws  DirectoryException  If the network group ID for the provided
@@ -188,9 +199,6 @@
      WorkflowElement[] postWorkflowElements
      ) throws DirectoryException
  {
    // true as soon as the workflow has been registered
    boolean registered = false;
    // Is it the rootDSE workflow?
    DN baseDN = workflow.getBaseDN();
    if (baseDN.isNullDN())
@@ -198,7 +206,6 @@
      // NOTE - The rootDSE workflow is stored with the registeredWorkflows.
      rootDSEWorkflowNode =
        new RootDseWorkflowTopology(workflow, namingContexts);
      registered = true;
    }
    else
    {
@@ -210,7 +217,6 @@
      // Register the workflow node with the network group. If the workflow
      // ID is already existing then an exception is raised.
      registerWorkflowNode(workflowNode);
      registered = true;
      // Now add the workflow in the workflow topology...
      for (WorkflowTopologyNode curNode: registeredWorkflowNodes.values())
@@ -234,17 +240,6 @@
      // Rebuild the list of naming context handled by the network group
      rebuildNamingContextList();
    }
    // If the workflow has been registered successfully then register it
    // with the default network group
    if (registered)
    {
      if (this != defaultNetworkGroup)
      {
        defaultNetworkGroup.registerWorkflow(
            workflow, preWorkflowElements, postWorkflowElements);
      }
    }
  }
@@ -253,20 +248,25 @@
   * deregister is identified by its baseDN.
   *
   * @param baseDN  the baseDN of the workflow to deregister, may be null
   *
   * @return the deregistered workflow
   */
  public void deregisterWorkflow(
  public Workflow deregisterWorkflow(
      DN baseDN
      )
  {
    Workflow workflow = null;
    if (baseDN == null)
    {
      return;
      return workflow;
    }
    if (baseDN.isNullDN())
    {
      // deregister the rootDSE
      deregisterWorkflow(rootDSEWorkflowNode);
      workflow = rootDSEWorkflowNode.getWorkflowImpl();
    }
    else
    {
@@ -281,6 +281,7 @@
            // Call deregisterWorkflow() instead of deregisterWorkflowNode()
            // because we want the naming context list to be updated as well.
            deregisterWorkflow(node);
            workflow = node.getWorkflowImpl();
            // Only one workflow can match the baseDN, so we can break
            // the loop here.
@@ -289,6 +290,8 @@
        }
      }
    }
    return workflow;
  }
@@ -505,38 +508,6 @@
  /**
   * Checks whether a base DN has been already registered with
   * the network group.
   *
   * @param baseDN  the base DN to check
   * @return <code>false</code> if the base DN is registered with the
   *         network group, <code>false</code> otherwise
   */
  private boolean baseDNAlreadyRegistered(
      DN baseDN
      )
  {
    // returned result
    boolean alreadyRegistered = false;
    // go through the list of registered workflow and check whether a base DN
    // has already been used in a registered workflow
    for (WorkflowTopologyNode workflowNode: registeredWorkflowNodes.values())
    {
      DN curDN = workflowNode.getBaseDN();
      if (baseDN.equals (curDN))
      {
        alreadyRegistered = true;
        break;
      }
    }
    // check done
    return alreadyRegistered;
  }
  /**
   * Returns the list of naming contexts handled by the network group.
   *
   * @return the list of naming contexts
@@ -614,5 +585,47 @@
    namingContexts = null;
    networkGroupID = null;
    rootDSEWorkflowNode = null;
    registeredWorkflowNodes = null;
  }
  /**
   * Provides the list of network group registered with the server.
   *
   * @return the list of registered network groups
   */
  public static Collection<NetworkGroup> getRegisteredNetworkGroups()
  {
    return registeredNetworkGroups.values();
  }
  /**
   * Resets the configuration of all the registered network groups.
   */
  public static void resetConfig()
  {
    // Reset the default network group
    defaultNetworkGroup.reset();
    // Reset all the registered network group
    synchronized (registeredNetworkGroupsLock)
    {
      registeredNetworkGroups = new TreeMap<String, NetworkGroup>();
    }
  }
  /**
   * Resets the configuration of the current network group.
   */
  public void reset()
  {
    synchronized (registeredWorkflowNodesLock)
    {
      registeredWorkflowNodes = new TreeMap<String, WorkflowTopologyNode>();
      rootDSEWorkflowNode = null;
      namingContexts = new NetworkGroupNamingContexts();
    }
  }
}
opends/src/server/org/opends/server/core/NetworkGroupConfigManager.java
New file
@@ -0,0 +1,305 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.server.NetworkGroupCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
/**
 * This class defines a utility that will be used to manage the configuration
 * for the set of network groups defined in the Directory Server.
 * It will perform the necessary initialization of those network groups when
 * the server is first started, and then will manage any changes to them while
 * the server is running.
 */
public class NetworkGroupConfigManager
       implements ConfigurationChangeListener<NetworkGroupCfg>,
                  ConfigurationAddListener<NetworkGroupCfg>,
                  ConfigurationDeleteListener<NetworkGroupCfg>
{
  // A mapping between the DNs of the config entries and the associated
  // network groups.
  private ConcurrentHashMap<DN, NetworkGroup> networkGroups;
  /**
   * Creates a new instance of this network group config manager.
   */
  public NetworkGroupConfigManager()
  {
    networkGroups = new ConcurrentHashMap<DN, NetworkGroup>();
  }
  /**
   * Initializes all network groups currently defined in the Directory
   * Server configuration.  This should only be called at Directory Server
   * startup.
   *
   * @throws  ConfigException  If a configuration problem causes the network
   *                           group initialization process to fail.
   */
  public void initializeNetworkGroups()
      throws ConfigException
  {
    // Get the root configuration object.
    ServerManagementContext managementContext =
         ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
         managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so we
    // can be notified if any network group entries are added or removed.
    rootConfiguration.addNetworkGroupAddListener(this);
    rootConfiguration.addNetworkGroupDeleteListener(this);
    //Initialize the existing network groups.
    for (String networkGroupName : rootConfiguration.listNetworkGroups())
    {
      NetworkGroupCfg networkGroupConfiguration =
           rootConfiguration.getNetworkGroup(networkGroupName);
      networkGroupConfiguration.addChangeListener(this);
      if (networkGroupConfiguration.isEnabled())
      {
        try
        {
          createAndRegisterNetworkGroup(networkGroupConfiguration);
        }
        catch (DirectoryException de)
        {
          throw new ConfigException(de.getMessageObject());
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationAddAcceptable(
      NetworkGroupCfg configuration,
      List<Message>   unacceptableReasons)
  {
    // Nothing to check.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(
      NetworkGroupCfg configuration)
  {
    ResultCode         resultCode          = ResultCode.SUCCESS;
    boolean            adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    configuration.addChangeListener(this);
    // If the new network group is enabled then create it and register it.
    if (configuration.isEnabled())
    {
      try
      {
        createAndRegisterNetworkGroup(configuration);
      }
      catch (DirectoryException de)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = DirectoryServer.getServerErrorResultCode();
        }
        messages.add(de.getMessageObject());
      }
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationDeleteAcceptable(
      NetworkGroupCfg configuration,
      List<Message>   unacceptableReasons)
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(
      NetworkGroupCfg configuration)
  {
    ResultCode         resultCode          = ResultCode.SUCCESS;
    boolean            adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    NetworkGroup networkGroup = networkGroups.remove(configuration.dn());
    if (networkGroup != null)
    {
      networkGroup.deregister();
      networkGroup.finalizeNetworkGroup();
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      NetworkGroupCfg configuration,
      List<Message>   unacceptableReasons)
  {
    // Nothing to check.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      NetworkGroupCfg configuration)
  {
    ResultCode         resultCode          = ResultCode.SUCCESS;
    boolean            adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    ConfigChangeResult configChangeResult =
      new ConfigChangeResult(resultCode, adminActionRequired, messages);
    // Get the existing network group if it's already enabled.
    NetworkGroup existingNetworkGroup = networkGroups.get(configuration.dn());
    // If the new configuration has the network group disabled, then disable
    // it if it is enabled, or do nothing if it's already disabled.
    if (! configuration.isEnabled())
    {
      if (existingNetworkGroup != null)
      {
        networkGroups.remove(configuration.dn());
        existingNetworkGroup.deregister();
        existingNetworkGroup.finalizeNetworkGroup();
      }
      return configChangeResult;
    }
    // If the network group is disabled then create it and register it.
    if (existingNetworkGroup == null)
    {
      try
      {
        createAndRegisterNetworkGroup(configuration);
      }
      catch (DirectoryException de)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = DirectoryServer.getServerErrorResultCode();
        }
        messages.add(de.getMessageObject());
      }
    }
    return configChangeResult;
  }
  /**
   * Creates and registers a network group.
   *
   * @param networkGroupCfg  the network group configuration
   *
   * @throws DirectoryException If a problem occurs while trying to
   *                            register a network group.
   */
  private void createAndRegisterNetworkGroup(
      NetworkGroupCfg networkGroupCfg
      ) throws DirectoryException
  {
    // create the network group
    String networkGroupId = networkGroupCfg.getNetworkGroupId();
    NetworkGroup networkGroup = new NetworkGroup(networkGroupId);
    // register the workflows with the network group
    for (String workflowID: networkGroupCfg.getWorkflow())
    {
      WorkflowImpl workflowImpl =
        (WorkflowImpl) WorkflowImpl.getWorkflow(workflowID);
      networkGroup.registerWorkflow(workflowImpl);
    }
    // finally register the network group with the server
    networkGroups.put(networkGroupCfg.dn(), networkGroup);
    networkGroup.register();
  }
}
opends/src/server/org/opends/server/core/RootDseWorkflowTopology.java
@@ -166,8 +166,10 @@
  {
    StringBuilder sb = new StringBuilder();
    // display the baseDN
    sb.append(leftMargin + "Workflow baseDN:[ \"\" ]\n");
    // display the identifier and baseDN
    String workflowID = this.getWorkflowImpl().getWorkflowId();
    sb.append(leftMargin + "Workflow ID = " + workflowID + "\n");
    sb.append(leftMargin + "         baseDN:[ \"\" ]\n");
    return sb;
  }
opends/src/server/org/opends/server/core/WorkflowConfigManager.java
New file
@@ -0,0 +1,313 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.admin.std.server.WorkflowCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
import org.opends.server.workflowelement.WorkflowElement;
/**
 * This class defines a utility that will be used to manage the configuration
 * for the set of workflows defined in the Directory Server.  It will perform
 * the necessary initialization of those workflows when the server is first
 * started, and then will manage any changes to them while the server is
 * running.
 */
public class WorkflowConfigManager
       implements ConfigurationChangeListener<WorkflowCfg>,
                  ConfigurationAddListener<WorkflowCfg>,
                  ConfigurationDeleteListener<WorkflowCfg>
{
  // A mapping between the DNs of the config entries and the associated
  // workflows.
  private ConcurrentHashMap<DN, WorkflowImpl> workflows;
  /**
   * Creates a new instance of this workflow config manager.
   */
  public WorkflowConfigManager()
  {
    workflows = new ConcurrentHashMap<DN, WorkflowImpl>();
  }
  /**
   * Initializes all workflows currently defined in the Directory
   * Server configuration.  This should only be called at Directory Server
   * startup.
   *
   * @throws  ConfigException  If a configuration problem causes the workflow
   *                           initialization process to fail.
   */
  public void initializeWorkflows()
      throws ConfigException
  {
    // Get the root configuration object.
    ServerManagementContext managementContext =
         ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
         managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so we
    // can be notified if any workflow entries are added or removed.
    rootConfiguration.addWorkflowAddListener(this);
    rootConfiguration.addWorkflowDeleteListener(this);
    //Initialize the existing workflows.
    for (String workflowName : rootConfiguration.listWorkflows())
    {
      WorkflowCfg workflowConfiguration =
        rootConfiguration.getWorkflow(workflowName);
      workflowConfiguration.addChangeListener(this);
      if (workflowConfiguration.isEnabled())
      {
        try
        {
          createAndRegisterWorkflow(workflowConfiguration);
        }
        catch (DirectoryException de)
        {
          throw new ConfigException(de.getMessageObject());
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationAddAcceptable(
      WorkflowCfg   configuration,
      List<Message> unacceptableReasons)
  {
    // Nothing to check.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(
      WorkflowCfg configuration)
  {
    ResultCode         resultCode          = ResultCode.SUCCESS;
    boolean            adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    configuration.addChangeListener(this);
    // If the new network group is enabled then create it and register it.
    if (configuration.isEnabled())
    {
      try
      {
        createAndRegisterWorkflow(configuration);
      }
      catch (DirectoryException de)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = DirectoryServer.getServerErrorResultCode();
        }
        messages.add(de.getMessageObject());
      }
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationDeleteAcceptable(
      WorkflowCfg   configuration,
      List<Message> unacceptableReasons)
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(
      WorkflowCfg configuration)
  {
    ResultCode         resultCode          = ResultCode.SUCCESS;
    boolean            adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    WorkflowImpl workflow = workflows.remove(configuration.dn());
    if (workflow != null)
    {
      workflow.deregister();
      workflow.finalizeWorkflow();
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      WorkflowCfg   configuration,
      List<Message> unacceptableReasons)
  {
    // Nothing to check.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      WorkflowCfg configuration)
  {
    ResultCode         resultCode          = ResultCode.SUCCESS;
    boolean            adminActionRequired = false;
    ArrayList<Message> messages            = new ArrayList<Message>();
    ConfigChangeResult configChangeResult =
      new ConfigChangeResult(resultCode, adminActionRequired, messages);
    // Get the existing network group if it's already enabled.
    WorkflowImpl existingWorkflow = workflows.get(configuration.dn());
    // If the new configuration has the validator disabled, then disable it if
    // it is enabled, or do nothing if it's already disabled.
    if (! configuration.isEnabled())
    {
      if (existingWorkflow != null)
      {
        workflows.remove(configuration.dn());
        existingWorkflow.deregister();
        existingWorkflow.finalizeWorkflow();
      }
      return configChangeResult;
    }
    // If the network group is disabled then create and register it.
    if (existingWorkflow == null)
    {
      try
      {
        createAndRegisterWorkflow(configuration);
      }
      catch (DirectoryException de)
      {
        if (resultCode == ResultCode.SUCCESS)
        {
          resultCode = DirectoryServer.getServerErrorResultCode();
        }
        messages.add(de.getMessageObject());
      }
    }
    return configChangeResult;
  }
  /**
   * Creates a workflow, registers the workflow with the server
   * and registers the workflow with the default network group.
   *
   * @param workflowCfg  the workflow configuration
   *
   * @throws DirectoryException If a problem occurs while trying to
   *                            decode a provided string as a DN or if
   *                            the workflow ID for a provided workflow
   *                            conflicts with the workflow ID of an existing
   *                            workflow during workflow registration.
   */
  private void createAndRegisterWorkflow(
      WorkflowCfg workflowCfg
      ) throws DirectoryException
  {
    // The ID of the workflow to create
    String workflowId = workflowCfg.getWorkflowId();
    // Create the root workflow element to associate with the workflow
    String rootWorkflowElementID = workflowCfg.getWorkflowElement();
    WorkflowElement rootWorkflowElement =
      WorkflowElement.getWorkflowElement(rootWorkflowElementID);
    // Get the base DN targeted by the workflow
    DN baseDN = DN.decode(workflowCfg.getBaseDN());
    // Create the workflow and register it with the server
    WorkflowImpl workflowImpl =
      new WorkflowImpl(workflowId, baseDN, rootWorkflowElement);
    workflows.put(workflowCfg.dn(), workflowImpl);
    workflowImpl.register();
    // Register the workflow with the default network group
    NetworkGroup.getDefaultNetworkGroup().registerWorkflow(workflowImpl);
  }
}
opends/src/server/org/opends/server/core/WorkflowImpl.java
@@ -30,6 +30,7 @@
import org.opends.messages.Message;
import static org.opends.server.util.Validator.ensureNotNull;
import java.util.Collection;
import java.util.TreeMap;
import org.opends.server.types.DN;
@@ -108,6 +109,17 @@
  /**
   * Performs any finalization that might be required when this
   * workflow is unloaded.  No action is taken in the default
   * implementation.
   */
  public void finalizeWorkflow()
  {
    // No action is required by default.
  }
  /**
   * Gets the base DN of the data set being handled by the workflow.
   *
   * @return the workflow base DN
@@ -230,6 +242,7 @@
    return workflowToDeregister;
  }
  /**
   * Deregisters all Workflows that have been registered.  This should be
   * called when the server is shutting down.
@@ -242,4 +255,52 @@
        new TreeMap<String, Workflow>();
    }
  }
  /**
   * Gets a workflow that was registered with the server.
   *
   * @param workflowID  the ID of the workflow to get
   * @return the requested workflow
   */
  public static Workflow getWorkflow(
      String workflowID)
  {
    return registeredWorkflows.get(workflowID);
  }
  /**
   * Gets all the workflows that were registered with the server.
   *
   * @return the list of registered workflows
   */
  public static Collection<Workflow> getWorkflows()
  {
    return registeredWorkflows.values();
  }
  /**
   * Gets the root workflow element for test purpose only.
   *
   * @return the root workflow element.
   */
  WorkflowElement getRootWorkflowElement()
  {
    return rootWorkflowElement;
  }
  /**
   * Resets all the registered workflows.
   */
  public static void resetConfig()
  {
    synchronized (registeredWorkflowsLock)
    {
      registeredWorkflows = new TreeMap<String, Workflow>();
    }
  }
}
opends/src/server/org/opends/server/core/WorkflowTopologyNode.java
@@ -500,7 +500,9 @@
    // display the baseDN
    DN baseDN = getBaseDN();
    sb.append(leftMargin + "Workflow baseDN:[");
    String workflowID = this.getWorkflowImpl().getWorkflowId();
    sb.append(leftMargin + "Workflow ID = " + workflowID + "\n");
    sb.append(leftMargin + "         baseDN:[");
    if (baseDN.isNullDN())
    {
      sb.append(" \"\"");
@@ -511,21 +513,26 @@
    }
    sb.append(" ]\n");
    // display the root workflow element
    sb.append(leftMargin
        + "         Root Workflow Element: "
        + getWorkflowImpl().getRootWorkflowElement() + "\n");
    // display parent workflow
    sb.append(leftMargin + "Parent: " + getParent() + "\n");
    sb.append(leftMargin + "         Parent: " + getParent() + "\n");
    // dump each subordinate
    sb.append(leftMargin + "List of subordinates:\n");
    sb.append(leftMargin + "         List of subordinates:\n");
    ArrayList<WorkflowTopologyNode> subordinates = getSubordinates();
    if (subordinates.isEmpty())
    {
      sb.append(leftMargin + "   NONE\n");
      sb.append(leftMargin + "            NONE\n");
    }
    else
    {
      for (WorkflowTopologyNode subordinate: getSubordinates())
      {
        sb.append(subordinate.toString(leftMargin + "   "));
        sb.append(subordinate.toString(leftMargin + "            "));
      }
    }
opends/src/server/org/opends/server/workflowelement/LeafWorkflowElement.java
@@ -26,24 +26,21 @@
 */
package org.opends.server.workflowelement;
import org.opends.server.admin.std.server.WorkflowElementCfg;
/**
 * This class gathers all the workflow elements that are encapsulating
 * physical repositories such as local database, remote LDAP servers,
 * JDBC repository or LDIF flat file.
 * This class defines the super class for all the workflow elements
 * used to wrap physical repositories. A physical repository contains
 * data (for example, a local backend, a remote LDAP servers or an
 * LDIF flat file). Such workflow element is a leaf in the sense that
 * the workflow element can be used by another workflow element but
 * cannot use an other workflow element.
 *
 * @param  <T>  The type of configuration handled by this workflow elelemnt.
 */
public abstract class LeafWorkflowElement
  extends WorkflowElement
public abstract class LeafWorkflowElement <T extends WorkflowElementCfg>
  extends WorkflowElement<WorkflowElementCfg>
{
  /**
   * Creates a new instance of the leaf workflow element.
   *
   * @param workflowElementID  the workflow element identifier as defined
   *                           in the configuration.
   */
  protected LeafWorkflowElement(String workflowElementID)
  {
    super(workflowElementID);
  }
}
opends/src/server/org/opends/server/workflowelement/WorkflowElement.java
@@ -26,6 +26,15 @@
 */
package org.opends.server.workflowelement;
import static org.opends.server.util.Validator.ensureNotNull;
import static org.opends.messages.ConfigMessages.*;
import java.util.List;
import java.util.TreeMap;
import org.opends.messages.Message;
import org.opends.server.admin.std.server.WorkflowElementCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.types.Operation;
@@ -37,25 +46,48 @@
 * case for load balancing and distribution. And workflow element can be used
 * in a virtual environment to transform data (DN and attribute renaming,
 * attribute value renaming...).
 *
 * @param  <T>  The type of configuration handled by this workflow elelemnt.
 */
public abstract class WorkflowElement
       <T extends WorkflowElementCfg>
{
  // Indicates whether the workflow element encapsulates a private local
  // backend.
  private boolean isPrivate = false;
  // The workflow element identifier.
  private String workflowElementID = null;
  // The set of workflow elements registered with the server.
  // The workflow element identifier is used as a key in the map.
  private static TreeMap<String, WorkflowElement> registeredWorkflowElements =
    new TreeMap<String, WorkflowElement>();
  // A lock to protect access to the registered workflow elements.
  private static Object registeredWorkflowElementsLock = new Object();
  /**
   * Creates a new instance of the workflow element.
   */
  public WorkflowElement()
  {
    // There is nothing to do in the constructor.
  }
  /**
   * Initializes the instance of the workflow element.
   *
   * @param workflowElementID  the workflow element identifier as defined
   *                           in the configuration.
   */
  public WorkflowElement(String workflowElementID)
  public void initialize(String workflowElementID)
  {
    this.workflowElementID = workflowElementID;
  }
@@ -63,6 +95,41 @@
  /**
   * Indicates whether the provided configuration is acceptable for
   * this workflow elelement.
   *
   * @param  configuration        The workflow element configuration for
   *                              which to make the determination.
   * @param  unacceptableReasons  A list that may be used to hold the
   *                              reasons that the provided
   *                              configuration is not acceptable.
   *
   * @return  {@code true} if the provided configuration is acceptable
   *          for this workflow element, or {@code false} if not.
   */
  public boolean isConfigurationAcceptable(
      WorkflowElementCfg configuration,
      List<String> unacceptableReasons)
  {
    // This default implementation does not perform any special
    // validation.  It should be overridden by workflow element
    // implementations that wish to perform more detailed validation.
    return true;
  }
  /**
   * Performs any finalization that might be required when this
   * workflow element is unloaded.  No action is taken in the default
   * implementation.
   */
  public void finalizeWorkflowElement()
  {
    // No action is required by default.
  }
  /**
   * Executes the workflow element for an operation.
   *
   * @param operation the operation to execute
@@ -108,5 +175,78 @@
  {
    return workflowElementID;
  }
  /**
   * Registers the workflow element (this) with the server.
   *
   * @throws  ConfigException  If the workflow element ID for the provided
   *                           workflow element conflicts with the workflow
   *                           element ID of an existing workflow element.
   */
  public void register()
      throws ConfigException
  {
    ensureNotNull(workflowElementID);
    synchronized (registeredWorkflowElementsLock)
    {
      // the workflow element must not be already registered
      if (registeredWorkflowElements.containsKey(workflowElementID))
      {
        Message message = ERR_CONFIG_WORKFLOW_ELEMENT_ALREADY_REGISTERED.get(
            workflowElementID);
        throw new ConfigException(message);
      }
      TreeMap<String, WorkflowElement> newWorkflowElements =
        new TreeMap<String, WorkflowElement>(registeredWorkflowElements);
      newWorkflowElements.put(workflowElementID, this);
      registeredWorkflowElements = newWorkflowElements;
    }
  }
  /**
   * Deregisters the workflow element (this) with the server.
   */
  public void deregister()
  {
    ensureNotNull(workflowElementID);
    synchronized (registeredWorkflowElementsLock)
    {
      TreeMap<String, WorkflowElement> newWorkflowElements =
        new TreeMap<String, WorkflowElement>(registeredWorkflowElements);
      newWorkflowElements.remove(workflowElementID);
      registeredWorkflowElements = newWorkflowElements;
    }
  }
  /**
   * Gets a workflow element that was registered with the server.
   *
   * @param workflowElementID  the ID of the workflow element to get
   * @return the requested workflow element
   */
  public static WorkflowElement getWorkflowElement(
      String workflowElementID)
  {
    return registeredWorkflowElements.get(workflowElementID);
  }
  /**
   * Resets all the registered workflows.
   */
  public static void resetConfig()
  {
    synchronized (registeredWorkflowElementsLock)
    {
      registeredWorkflowElements = new TreeMap<String, WorkflowElement>();
    }
  }
}
opends/src/server/org/opends/server/workflowelement/WorkflowElementConfigManager.java
New file
@@ -0,0 +1,448 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.messages.ConfigMessages.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.messages.Message;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.server.ConfigurationAddListener;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.server.ConfigurationDeleteListener;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.meta.WorkflowElementCfgDefn;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.admin.std.server.WorkflowElementCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
/**
 * This class defines a utility that will be used to manage the configuration
 * for the set of workflow elements defined in the Directory Server.
 * It will perform the necessary initialization of those backends when the
 * server is first started, and then will manage any changes to them while
 * the server is running.
 */
public class WorkflowElementConfigManager
       implements ConfigurationChangeListener<WorkflowElementCfg>,
                  ConfigurationAddListener   <WorkflowElementCfg>,
                  ConfigurationDeleteListener<WorkflowElementCfg>
{
  // A mapping between the DNs of the config entries and the associated
  // workflow elements.
  private ConcurrentHashMap<DN, WorkflowElement> workflowElements;
  /**
   * Creates a new instance of this workflow config manager.
   */
  public WorkflowElementConfigManager()
  {
    workflowElements = new ConcurrentHashMap<DN, WorkflowElement>();
  }
  /**
   * Initializes all workflow elements currently defined in the Directory
   * Server configuration.  This should only be called at Directory Server
   * startup.
   *
   * @throws  ConfigException  If a configuration problem causes the workflow
   *                           element initialization process to fail.
   * @throws InitializationException If a problem occurs while the workflow
   *                                 element is loaded and registered with
   *                                 the server
   */
  public void initializeWorkflowElements()
      throws ConfigException, InitializationException
  {
    // Get the root configuration object.
    ServerManagementContext managementContext =
         ServerManagementContext.getInstance();
    RootCfg rootConfiguration =
         managementContext.getRootConfiguration();
    // Register as an add and delete listener with the root configuration so we
    // can be notified if any workflow element entries are added or removed.
    rootConfiguration.addWorkflowElementAddListener(this);
    rootConfiguration.addWorkflowElementDeleteListener(this);
    //Initialize the existing workflows.
    for (String workflowName : rootConfiguration.listWorkflowElements())
    {
      WorkflowElementCfg workflowConfiguration =
        rootConfiguration.getWorkflowElement(workflowName);
      workflowConfiguration.addChangeListener(this);
      if (workflowConfiguration.isEnabled())
      {
        loadAndRegisterWorkflowElement(workflowConfiguration);
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationAddAcceptable(
      WorkflowElementCfg configuration,
      List<Message> unacceptableReasons)
  {
    boolean isAcceptable = true;
    if (configuration.isEnabled())
    {
      // Get the name of the class and make sure we can instantiate it as
      // a workflow element.
      String className = configuration.getJavaClass();
      try
      {
        // Load the class but don't initialize it.
        loadWorkflowElement(className, configuration, false);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add (ie.getMessageObject());
        isAcceptable = false;
      }
    }
    return isAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationAdd(
      WorkflowElementCfg configuration)
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<Message>()
        );
    configuration.addChangeListener(this);
    // If the new workflow element is enabled then create it and register it.
    if (configuration.isEnabled())
    {
      try
      {
        loadAndRegisterWorkflowElement(configuration);
      }
      catch (InitializationException de)
      {
        if (changeResult.getResultCode() == ResultCode.SUCCESS)
        {
          changeResult.setResultCode(
              DirectoryServer.getServerErrorResultCode());
        }
        changeResult.addMessage(de.getMessageObject());
      }
    }
    return changeResult;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationDeleteAcceptable(
      WorkflowElementCfg configuration,
      List<Message> unacceptableReasons)
  {
    // FIXME -- We should try to perform some check to determine whether the
    // worklfow element is in use.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationDelete(
      WorkflowElementCfg configuration)
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<Message>()
        );
    WorkflowElement workflowElement =
      workflowElements.remove(configuration.dn());
    if (workflowElement != null)
    {
      workflowElement.deregister();
      workflowElement.finalizeWorkflowElement();
    }
    return changeResult;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      WorkflowElementCfg configuration,
      List<Message> unacceptableReasons)
  {
    boolean isAcceptable = true;
    if (configuration.isEnabled())
    {
      // Get the name of the class and make sure we can instantiate it as
      // a workflow element.
      String className = configuration.getJavaClass();
      try
      {
        // Load the class but don't initialize it.
        loadWorkflowElement(className, configuration, false);
      }
      catch (InitializationException ie)
      {
        unacceptableReasons.add (ie.getMessageObject());
        isAcceptable = false;
      }
    }
    return isAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      WorkflowElementCfg configuration)
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<Message>()
        );
    // Get the existing workflow element if it's already enabled.
    WorkflowElement existingWorkflowElement =
      workflowElements.get(configuration.dn());
    // If the new configuration has the workflow element disabled,
    // then disable it if it is enabled, or do nothing if it's already disabled.
    if (! configuration.isEnabled())
    {
      if (existingWorkflowElement != null)
      {
        workflowElements.remove(configuration.dn());
        existingWorkflowElement.deregister();
        existingWorkflowElement.finalizeWorkflowElement();
      }
      return changeResult;
    }
    // If the workflow element is disabled then create it and register it.
    if (existingWorkflowElement == null)
    {
      try
      {
        loadAndRegisterWorkflowElement(configuration);
      }
      catch (InitializationException de)
      {
        if (changeResult.getResultCode() == ResultCode.SUCCESS)
        {
          changeResult.setResultCode(
              DirectoryServer.getServerErrorResultCode());
        }
        changeResult.addMessage(de.getMessageObject());
      }
    }
    return changeResult;
  }
  /**
   * Loads a class and instanciates it as a workflow element. The workflow
   * element is initialized and registered with the server.
   *
   * @param workflowCfg  the workflow element configuration
   *
   * @throws InitializationException If a problem occurs while trying to
   *                            decode a provided string as a DN or if
   *                            the workflow element ID for a provided
   *                            workflow element conflicts with the workflow
   *                            ID of an existing workflow during workflow
   *                            registration.
   */
  private void loadAndRegisterWorkflowElement(
      WorkflowElementCfg workflowElementCfg
      ) throws InitializationException
  {
    // Load the workflow element class
    String className = workflowElementCfg.getJavaClass();
    WorkflowElement workflowElement =
      loadWorkflowElement(className, workflowElementCfg, true);
    try
    {
      // register the workflow element
      workflowElement.register();
      // keep the workflow element in the list of configured workflow
      // elements
      workflowElements.put(workflowElementCfg.dn(), workflowElement);
    }
    catch (ConfigException de)
    {
      throw new InitializationException(de.getMessageObject());
    }
  }
  /**
   * Loads a class and instanciates it as a workflow element. If requested
   * initializes the newly created instance.
   *
   * @param  className      The fully-qualified name of the workflow element
   *                        class to load, instantiate, and initialize.
   * @param  configuration  The configuration to use to initialize the workflow
   *                        element.  It must not be {@code null}.
   * @param  initialize     Indicates whether the workflow element instance
   *                        should be initialized.
   *
   * @return  The possibly initialized workflow element.
   *
   * @throws  InitializationException  If a problem occurred while attempting
   *                                   to initialize the workflow element.
   */
  private WorkflowElement loadWorkflowElement(
      String className,
      WorkflowElementCfg configuration,
      boolean initialize
      ) throws InitializationException
  {
    try
    {
      WorkflowElementCfgDefn              definition;
      ClassPropertyDefinition             propertyDefinition;
      Class<? extends WorkflowElement>    workflowElementClass;
      WorkflowElement<? extends WorkflowElementCfg> workflowElement;
      definition = WorkflowElementCfgDefn.getInstance();
      propertyDefinition =
        definition.getJavaClassPropertyDefinition();
      workflowElementClass =
        propertyDefinition.loadClass(className, WorkflowElement.class);
      workflowElement =
        (WorkflowElement<? extends WorkflowElementCfg>)
          workflowElementClass.newInstance();
      if (initialize)
      {
        Method method = workflowElement.getClass().getMethod(
            "initializeWorkflowElement",
            configuration.definition().getServerConfigurationClass()
            );
        method.invoke(workflowElement, configuration);
      }
      else
      {
        Method method = workflowElement.getClass().getMethod(
            "isConfigurationAcceptable",
            WorkflowElementCfg.class,
            List.class);
        List<String> unacceptableReasons = new ArrayList<String>();
        Boolean acceptable = (Boolean) method.invoke(
            workflowElement, configuration, unacceptableReasons);
        if (! acceptable)
        {
          StringBuilder buffer = new StringBuilder();
          if (! unacceptableReasons.isEmpty())
          {
            Iterator<String> iterator = unacceptableReasons.iterator();
            buffer.append(iterator.next());
            while (iterator.hasNext())
            {
              buffer.append(".  ");
              buffer.append(iterator.next());
            }
          }
          Message message =
            ERR_CONFIG_WORKFLOW_ELEMENT_CONFIG_NOT_ACCEPTABLE.get(
              String.valueOf(configuration.dn()), buffer.toString());
          throw new InitializationException(message);
        }
      }
      return workflowElement;
    }
    catch (Exception e)
    {
      Message message =
        ERR_CONFIG_WORKFLOW_ELEMENT_CANNOT_INITIALIZE.get(
            className, String.valueOf(configuration.dn()),
            stackTraceToSingleLineString(e));
      throw new InitializationException(message);
    }
  }
}
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -32,15 +32,23 @@
import java.util.List;
import java.util.TreeMap;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.LocalBackendWorkflowElementCfg;
import org.opends.server.api.Backend;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Operation;
import org.opends.server.types.ResultCode;
import org.opends.server.workflowelement.LeafWorkflowElement;
@@ -49,37 +57,152 @@
 * This class defines a local backend workflow element; e-g an entity that
 * handle the processing of an operation aginst a local backend.
 */
public class LocalBackendWorkflowElement extends LeafWorkflowElement
public class LocalBackendWorkflowElement extends
    LeafWorkflowElement<LocalBackendWorkflowElementCfg>
    implements ConfigurationChangeListener<LocalBackendWorkflowElementCfg>
{
  // the backend associated with the local workflow element
  private Backend backend;
  // the set of local backend workflow elements registered with the server
  private static TreeMap<String, LocalBackendWorkflowElement>
       registeredLocalBackends =
            new TreeMap<String, LocalBackendWorkflowElement>();
  // a lock to guarantee safe concurrent access to the registeredLocalBackends
  // variable
  private static Object registeredLocalBackendsLock = new Object();
  /**
   * Creates a new instance of the local backend workflow element.
   */
  public LocalBackendWorkflowElement()
  {
    // There is nothing to do in this constructor.
  }
  /**
   * Initializes a new instance of the local backend workflow element.
   * This method is intended to be called by DirectoryServer when
   * workflow configuration mode is auto as opposed to
   * initializeWorkflowElement which is invoked when workflow
   * configuration mode is manual.
   *
   * @param workflowElementID  the workflow element identifier
   * @param backend  the backend associated to that workflow element
   */
  private LocalBackendWorkflowElement(String workflowElementID, Backend backend)
  private void initialize(String workflowElementID, Backend backend)
  {
    super(workflowElementID);
    // Initialize the workflow ID
    super.initialize(workflowElementID);
    this.backend  = backend;
    setPrivate(backend.isPrivateBackend());
    if (this.backend != null)
    {
      setPrivate(this.backend.isPrivateBackend());
    }
  }
  /**
   * {@inheritDoc}
   */
  public void initializeWorkflowElement(
      LocalBackendWorkflowElementCfg configuration
      ) throws ConfigException, InitializationException
  {
    configuration.addLocalBackendChangeListener(this);
    // Read configuration and apply changes.
    processWorkflowElementConfig(configuration, true);
  }
  /**
   * {@inheritDoc}
   */
  public void finalizeWorkflowElement()
  {
    // null all fields so that any use of the finalized object will raise
    // an NPE
    super.initialize(null);
    backend = null;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      LocalBackendWorkflowElementCfg configuration,
      List<Message>                  unacceptableReasons
      )
  {
    boolean isAcceptable =
      processWorkflowElementConfig(configuration, false);
    return isAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
      LocalBackendWorkflowElementCfg configuration
      )
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<Message>()
        );
    processWorkflowElementConfig(configuration, true);
    return changeResult;
  }
  /**
   * Parses the provided configuration and configure the workflow element.
   *
   * @param configuration  The new configuration containing the changes.
   * @param applyChanges   If true then take into account the new configuration.
   *
   * @return  <code>true</code> if the configuration is acceptable.
   */
  private boolean processWorkflowElementConfig(
      LocalBackendWorkflowElementCfg configuration,
      boolean                        applyChanges
      )
  {
    // returned status
    boolean isAcceptable = true;
    // If the workflow element is disabled then do nothing. Note that the
    // config manager could have finalized the object right before.
    if (configuration.isEnabled())
    {
      // Read configuration.
      String newBackendID = configuration.getBackend();
      Backend newBackend  = DirectoryServer.getBackend(newBackendID);
      // Get the new config
      if (applyChanges)
      {
        super.initialize(configuration.getWorkflowElementId());
        backend = newBackend;
      }
    }
    return isAcceptable;
  }
  /**
   * Creates and registers a local backend with the server.
@@ -92,8 +215,9 @@
   *         already created or a newly created local backend workflow
   *         element.
   */
  public static LocalBackendWorkflowElement create(String workflowElementID,
                                                   Backend backend)
  public static LocalBackendWorkflowElement createAndRegister(
      String workflowElementID,
      Backend backend)
  {
    LocalBackendWorkflowElement localBackend = null;
@@ -101,8 +225,8 @@
    localBackend = registeredLocalBackends.get(workflowElementID);
    if (localBackend == null)
    {
      localBackend = new LocalBackendWorkflowElement(workflowElementID,
                                                     backend);
      localBackend = new LocalBackendWorkflowElement();
      localBackend.initialize(workflowElementID, backend);
      // store the new local backend in the list of registered backends
      registerLocalBackend(localBackend);
opends/tests/unit-tests-testng/src/server/org/opends/server/admin/ValidateConfigDefinitionsTest.java
@@ -133,6 +133,10 @@
                  "backend-id",
                  "plugin-type",
                  "replication-server-id",
                  "network-group-id",
                  "workflow-id",
                  "workflow-element-id",
                  "workflow-element"
                  // e.g. "prop-name-starting-with-object-prefix"
          });
opends/tests/unit-tests-testng/src/server/org/opends/server/core/WorkflowConfigurationTest.java
New file
@@ -0,0 +1,689 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import static org.opends.server.util.StaticUtils.createEntry;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.util.ArrayList;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.config.ConfigConstants;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.Entry;
import org.opends.server.types.ModificationType;
import org.opends.server.types.RawModification;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchScope;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.UtilTestCase;
import org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
 * This class tests the 'manual' workflow configuration mode. The 'auto'
 * configuration mode does not require any specific unit test because by
 * default the server is running with the 'auto' mode.
 *
 * With the manual configuration mode, all the network groups, workflows
 * and workflow elements must be defined in the configuration file.
 */
public class WorkflowConfigurationTest extends UtilTestCase
{
  // The base DN of the config backend
  private static final String configBaseDN = ConfigConstants.DN_CONFIG_ROOT;
  // The base DN of the rootDSE backend
  private static final String rootDSEBaseDN = "";
  // The workflow configuration mode attribute
  private static final String workflowModeAttributeType =
      "ds-cfg-workflow-configuration-mode";
  // The suffix attribute in a backend
  private static final String suffixAttributeType =
      "ds-cfg-base-dn";
  // The auto/manual modes
  private static final String workflowConfigModeAuto   = "auto";
  private static final String workflowConfigModeManual = "manual";
  //===========================================================================
  //                      B E F O R E    C L A S S
  //===========================================================================
  /**
   * Set up the environment for performing the tests in this suite.
   *
   * @throws Exception if the environment could not be set up.
   */
  @BeforeClass
  public void setUp()
    throws Exception
  {
    // Start the server so that we can update the configuration and execute
    // some LDAP operations
    TestCaseUtils.startServer();
    // Add the attribute ds-cfg-workflow-configuration-mode with the
    // value 'auto
    initializeConfigurationMode();
    checkBackendIsAccessible("o=test");
  }
  //===========================================================================
  //                    D A T A    P R O V I D E R
  //===========================================================================
  //===========================================================================
  //                           U T I L S
  //===========================================================================
  /**
   * Adds an attribute ds-cfg-workflow-configuration-mode in the entry
   * cn=config. The added value is 'auto'.
   */
  private void initializeConfigurationMode()
      throws Exception
  {
    // Add the ds-cfg-workflow-configuration-mode attribute and set
    // its value to "auto"
    ModifyOperationBasis modifyOperation = getModifyOperation(
        configBaseDN,
        ModificationType.ADD,
        workflowModeAttributeType,
        workflowConfigModeAuto);
    modifyOperation.run();
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Checks that test backend is accessible as well as config backend
   * and rootDSE backend.
   *
   * @param baseDN  the baseDN of the backend to check
   */
  private void checkBackendIsAccessible(String baseDN)
      throws Exception
  {
    // The config backend and rootDSE backend should always be accessible
    doSearch(rootDSEBaseDN, SearchScope.BASE_OBJECT, ResultCode.SUCCESS);
    doSearch(configBaseDN,  SearchScope.BASE_OBJECT, ResultCode.SUCCESS);
    // The test backend should be accessible
    doSearch(baseDN, SearchScope.BASE_OBJECT, ResultCode.SUCCESS);
  }
  /**
   * Checks that test backend is not accessible while config backend
   * and rootDSE backend are.
   *
   * @param baseDN  the baseDN of the backend to check
   */
  private void checkBackendIsNotAccessible(String baseDN)
      throws Exception
  {
    // The config backend and rootDSE should always be accessible
    doSearch(rootDSEBaseDN, SearchScope.BASE_OBJECT, ResultCode.SUCCESS);
    doSearch(configBaseDN,  SearchScope.BASE_OBJECT, ResultCode.SUCCESS);
    // The test backend should be accessible
    doSearch(baseDN, SearchScope.BASE_OBJECT, ResultCode.NO_SUCH_OBJECT);
  }
  /**
   * Sets the ds-cfg-workflow-configuration-mode attribute to 'auto'
   */
  private void setModeAuto() throws Exception
  {
    ModifyOperationBasis modifyOperation = getModifyOperation(
        configBaseDN,
        ModificationType.REPLACE,
        workflowModeAttributeType,
        workflowConfigModeAuto);
    modifyOperation.run();
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Sets the ds-cfg-workflow-configuration-mode attribute to 'auto'
   */
  private void setModeManual() throws Exception
  {
    ModifyOperationBasis modifyOperation = getModifyOperation(
        configBaseDN,
        ModificationType.REPLACE,
        workflowModeAttributeType,
        workflowConfigModeManual);
    modifyOperation.run();
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Performs a search on a provided base DN.
   *
   * @param baseDN              the search base DN
   * @param scope               the scope of the search
   * @param expectedResultCode  the expected result code
   *
   * @return the search operation used for the test
   */
  private InternalSearchOperation doSearch(
      String      baseDN,
      SearchScope scope,
      ResultCode  expectedResultCode
      ) throws Exception
  {
    InternalSearchOperation searchOperation = new InternalSearchOperation(
       InternalClientConnection.getRootConnection(),
       InternalClientConnection.nextOperationID(),
       InternalClientConnection.nextMessageID(),
       new ArrayList<Control>(),
       new ASN1OctetString(baseDN),
       scope,
       DereferencePolicy.NEVER_DEREF_ALIASES,
       Integer.MAX_VALUE,
       Integer.MAX_VALUE,
       false,
       LDAPFilter.decode("(objectClass=*)"),
       null, null);
    searchOperation.run();
    assertEquals(searchOperation.getResultCode(), expectedResultCode);
    return searchOperation;
  }
  /**
   * Provides a modify operation.
   *
   * @param entryDN        the DN of the entry targeted by the modify operation
   * @param modType        the type of the modification
   * @param attributeType  the type of the attribute to modify
   * @param attributeValue the value of the attribute to modify
   */
  private static ModifyOperationBasis getModifyOperation(
      String           entryDN,
      ModificationType modType,
      String           attributeType,
      String           attributeValue)
  {
    ArrayList<ASN1OctetString> ldapValues = new ArrayList<ASN1OctetString>();
    ldapValues.add(new ASN1OctetString(attributeValue));
    LDAPAttribute ldapAttr = new LDAPAttribute(attributeType, ldapValues);
    ArrayList<RawModification> ldapMods = new ArrayList<RawModification>();
    ldapMods.add(new LDAPModification(modType, ldapAttr));
    ModifyOperationBasis modifyOperation = new ModifyOperationBasis(
        InternalClientConnection.getRootConnection(),
        InternalClientConnection.nextOperationID(),
        InternalClientConnection.nextMessageID(),
        new ArrayList<Control>(),
        new ASN1OctetString(entryDN),
        ldapMods);
    return modifyOperation;
  }
  /**
   * Creates a workflow to handle a local backend. The default network
   * group is used.
   *
   * @param baseDN     the base DN of the workflow
   * @param backendID  the backend which contains the baseDN
   *
   * @return the newly created workflow
   */
  private WorkflowImpl createWorkflow(String baseDN, String backendID)
      throws Exception
  {
    // Get the backend
    Backend backend = DirectoryServer.getBackend(backendID);
    assertNotNull(backend);
    // Create the workflow element that wraps the local backend
    String workflowElementID = baseDN + "#" + backendID;
    LocalBackendWorkflowElement workflowElement =
      LocalBackendWorkflowElement.createAndRegister(workflowElementID, backend);
    // Create a workflow and register it with the server
    String workflowID = baseDN + "#" + backendID;
    WorkflowImpl workflowImpl = new WorkflowImpl(
        workflowID, DN.decode(baseDN), workflowElement);
    workflowImpl.register();
    // Register the workflow with the default network group
    NetworkGroup.getDefaultNetworkGroup().registerWorkflow(workflowImpl);
    return workflowImpl;
  }
  /**
   * Removes a workflow.
   *
   * @param baseDN     the base DN of the workflow
   * @param backendID  the backend which contains the baseDN
   */
  private void removeWorkflow(String baseDN, String backendID)
      throws Exception
  {
    // Elaborate the workflow ID
    String workflowID = baseDN + "#" + backendID;
    // Deregister the workflow with the default network group
    NetworkGroup.getDefaultNetworkGroup().deregisterWorkflow(workflowID);
    // Deregister the workflow with the server
    Workflow workflow = WorkflowImpl.getWorkflow(workflowID);
    WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
    workflowImpl.deregister();
  }
  /**
   * Adds a new suffix in a backend.
   *
   * @param baseDN     the DN of the suffix to add
   * @param backendID  the identifier of the backend to which the suffix
   *                   is added
   */
  private void addSuffix(String baseDN, String backendID)
      throws Exception
  {
    // Elaborate the DN of the backend config entry
    String backendDN = elaborateBackendDN(backendID);
    // Add a new suffix in the backend
    ModifyOperationBasis modifyOperation = getModifyOperation(
        backendDN,
        ModificationType.ADD,
        suffixAttributeType,
        baseDN);
    modifyOperation.run();
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Create a base entry for a new suffix.
   *
   * @param baseDN     the DN of the new base entry
   * @param backendID  the identifier of the backend
   */
  private void createBaseEntry(String baseDN, String backendID)
      throws Exception
  {
    Entry entry = StaticUtils.createEntry(DN.decode(baseDN));
    AddOperationBasis addOperation = new AddOperationBasis(
        InternalClientConnection.getRootConnection(),
        InternalClientConnection.nextOperationID(),
        InternalClientConnection.nextMessageID(),
        null,
        entry.getDN(),
        entry.getObjectClasses(),
        entry.getUserAttributes(),
        entry.getOperationalAttributes());
    addOperation.run();
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Removes a new suffix in a backend.
   *
   * @param baseDN     the DN of the suffix to remove
   * @param backendID  the identifier of the backend to which the suffix
   *                   is removed
   *
   * @throw Exception  if the backend does not exist or if the suffix
   *                   already exist in the backend
   */
  private void removeSuffix(String baseDN, String backendID)
      throws Exception
  {
    // Elaborate the DN of the backend config entry
    String backendDN = elaborateBackendDN(backendID);
    // Add a new suffix in the backend
    ModifyOperationBasis modifyOperation = getModifyOperation(
        backendDN,
        ModificationType.DELETE,
        suffixAttributeType,
        baseDN);
    modifyOperation.run();
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Elaborates a DN for a backend config entry.
   *
   * @param backendID  the identifier of the backend to retrieve
   */
  private String elaborateBackendDN(String backendID)
  {
    String backendDN =
        "ds-cfg-backend-id=" + backendID + ",cn=Backends,cn=config";
    return backendDN;
  }
  /**
   * Initializes a memory-based backend.
   *
   * @param  backendID        the identifier of the backend to create
   * @param  baseDN           the DN of the suffix to create
   * @param  createBaseEntry  indicate whether to automatically create the base
   *                          entry and add it to the backend.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private static void createBackend(
      String  backendID,
      String  baseDN,
      boolean createBaseEntry
      ) throws Exception
  {
    TestCaseUtils.dsconfig(
        "create-backend",
        "--backend-name", backendID,
        "--type", "memory",
        "--set", "base-dn:" + baseDN,
        "--set", "writability-mode:enabled",
        "--set", "enabled:true");
    if (createBaseEntry)
    {
      Backend backend = DirectoryServer.getBackend(backendID);
      Entry e = createEntry(DN.decode(baseDN));
      backend.addEntry(e, null);
    }
  }
  /**
   * Remove a backend.
   *
   * @param  backendID  the identifier of the backend to remove
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private static void removeMemoryBackend(
      String backendID
      ) throws Exception
  {
    TestCaseUtils.dsconfig(
        "delete-backend",
        "--backend-name", backendID);
  }
  //===========================================================================
  //                      T E S T    C A S E S
  //===========================================================================
  /**
   * This test checks the transition from mode 'auto' to 'manual' and
   * 'manual' back to 'auto'. In this test there is no configuration for
   * network group, workflow and workflow element.
   */
  @Test
  public void transitionAutoManualAuto() throws Exception
  {
    // Settings
    String testBaseDN = "o=test";
    // The ds-cfg-workflow-configuration-mode attribute value is "auto"
    // (default value), let's put the same value again. Putting the same
    // value should have no impact and we should be able to perform a search
    // on the test backend.
    setModeAuto();
    checkBackendIsAccessible(testBaseDN);
    // Change the ds-cfg-workflow-configuration-mode attribute value
    // to "manual". The workflows should be fully reconfigured. But as
    // there is no configuration for the workflows, only cn=config and
    // rootDSE should be accessible.
    setModeManual();
    checkBackendIsNotAccessible(testBaseDN);
    // Change the ds-cfg-workflow-configuration-mode attribute value
    // back to "auto". All the local backends should be accessible again.
    setModeAuto();
    checkBackendIsAccessible(testBaseDN);
  }
  /**
   * This test checks the basic operation routing when configuration
   * mode is 'manual'. Few workflows are configured for the test.
   */
  @Test
  public void basicRoutingManualMode() throws Exception
  {
    // Settings
    String testBaseDN    = "o=test";
    String testBackendID = "test";
    // Workflow configuration mode is auto, so test backend should
    // be accessible
    checkBackendIsAccessible(testBaseDN);
    // Set the workflow configuration mode to manual. In this mode
    // no there is no workflow by default (but the config and rootDSE
    // workflows) so seaarches on the test backend should fail.
    setModeManual();
    checkBackendIsNotAccessible(testBaseDN);
    // Create a workflow to handle o=test backend then check that test
    // backend is now accessible
    createWorkflow(testBaseDN, testBackendID);
    checkBackendIsAccessible(testBaseDN);
    // Change workflow configuration mode back to auto and check that
    // test backend is still accessible
    setModeAuto();
    checkBackendIsAccessible(testBaseDN);
  }
  /**
   * This test checks the add/remove of suffix in a backend in manual
   * configuration mode.
   */
  @Test
  public void addRemoveSuffix() throws Exception
  {
    // Settings
    String testBaseDN2    = "o=addRemoveSuffix_1";
    String testBaseDN3    = "o=addRemoveSuffix_2";
    String testBackendID2 = "userRoot";
    // make sure we are in auto mode and check that the new suffixes are
    // not already defined on the server.
    setModeAuto();
    checkBackendIsNotAccessible(testBaseDN2);
    checkBackendIsNotAccessible(testBaseDN3);
    // Add a new suffix to the test backend and check that the new
    // suffix is accessible (we are in auto mode).
    addSuffix(testBaseDN2, testBackendID2);
    createBaseEntry(testBaseDN2, testBackendID2);
    checkBackendIsAccessible(testBaseDN2);
    // Remove the suffix and check that the removed suffix is no
    // more accessible.
    removeSuffix(testBaseDN2, testBackendID2);
    checkBackendIsNotAccessible(testBaseDN2);
    // Now move to the manual mode.
    setModeManual();
    // Add a new suffix and configure a workflow to route operation
    // to this new suffix, then check that the new suffix is accessible.
    // Note that before we can create a base entry we need to configure
    // first a workflow to route the ADD operation to the right suffix.
    // This need not be to be done in auto mode because with the auto mode
    // the workflow is automatically created when a new suffix is added.
    addSuffix(testBaseDN3, testBackendID2);
    createWorkflow(testBaseDN3, testBackendID2);
    createBaseEntry(testBaseDN3, testBackendID2);
    checkBackendIsAccessible(testBaseDN3);
    // Finally remove the new workflow and suffix and check that the suffix
    // is no more accessible.
    removeWorkflow(testBaseDN3, testBackendID2);
    removeSuffix(testBaseDN3, testBackendID2);
    checkBackendIsNotAccessible(testBaseDN3);
  }
  /**
   * This test checks the add/remove of a backend in manual configuration
   * mode.
   */
  @Test
  public void addRemoveBackend() throws Exception
  {
    // Local settings
    String backendID1 = "addRemoveBackend_1";
    String backendID2 = "addRemoveBackend_2";
    String baseDN1    = "o=addRemoveBackendBaseDN_1";
    String baseDN2    = "o=addRemoveBackendBaseDN_2";
    // Make sure we are in auto mode and check the suffix is not accessible
    setModeAuto();
    checkBackendIsNotAccessible(baseDN1);
    // Create a backend and check that the base entry is accessible.
    createBackend(backendID1, baseDN1, true);
    checkBackendIsAccessible(baseDN1);
    // Remove the backend and check that the suffix is no more accessible.
    removeMemoryBackend(backendID1);
    checkBackendIsNotAccessible(baseDN1);
    // Now move to the manual mode
    setModeManual();
    checkBackendIsNotAccessible(baseDN2);
    // Create a backend and create a workflow to route operations to that
    // new backend. Then check that the base entry is accessible.
    createBackend(backendID2, baseDN2, true);
    createWorkflow(baseDN2, backendID2);
    checkBackendIsAccessible(baseDN2);
    // Remove the workflow and the backend and check that the base entry
    // is no more accessible.
    removeWorkflow(baseDN2, backendID2);
    removeMemoryBackend(backendID2);
    checkBackendIsNotAccessible(baseDN2);
  }
  /**
   * This test checks the creation and utilization of network group
   * in the route process.
   */
  @Test
  public void useNetworkGroup() throws Exception
  {
    // Local settings
    String backendID = "test";
    String baseDN    = "o=test";
    // Now move to the manual mode
    setModeManual();
    // Create a route for o=test suffix in the default network group.
    // Search on o=test should succeed.
    WorkflowImpl workflowImpl = createWorkflow(baseDN, backendID);
    InternalSearchOperation searchOperation =
      doSearch(baseDN, SearchScope.BASE_OBJECT, ResultCode.SUCCESS);
    // Create a network group and store it in the client connection.
    // As the network group is empty, all searches should fail with a
    // no such object result code.
    String networkGroupID = "useNetworkGroupID";
    NetworkGroup networkGroup = new NetworkGroup(networkGroupID);
    ClientConnection clientConnection = searchOperation.getClientConnection();
    clientConnection.setNetworkGroup(networkGroup);
    searchOperation.run();
    assertEquals(searchOperation.getResultCode(), ResultCode.NO_SUCH_OBJECT);
    // Now register the o=test workflow and search again. The search
    // should succeed.
    networkGroup.registerWorkflow(workflowImpl);
    searchOperation.run();
    assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
    // Put back the default network group in the client conenction
    // and check that searches are still working.
    clientConnection.setNetworkGroup(NetworkGroup.getDefaultNetworkGroup());
    searchOperation.run();
    assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
  }
}