001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017
018package org.opends.guitools.controlpanel.task;
019
020import static org.opends.messages.AdminToolMessages.*;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.HashSet;
025import java.util.LinkedHashSet;
026import java.util.Set;
027import java.util.TreeSet;
028
029import javax.naming.NamingEnumeration;
030import javax.naming.NamingException;
031import javax.naming.directory.Attribute;
032import javax.naming.directory.BasicAttribute;
033import javax.naming.directory.DirContext;
034import javax.naming.directory.ModificationItem;
035import javax.naming.directory.SearchControls;
036import javax.naming.directory.SearchResult;
037import javax.swing.SwingUtilities;
038
039import org.opends.admin.ads.util.ConnectionUtils;
040import org.opends.guitools.controlpanel.browser.BrowserController;
041import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
042import org.opends.guitools.controlpanel.datamodel.BaseDNDescriptor;
043import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
044import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
045import org.opends.guitools.controlpanel.ui.ProgressDialog;
046import org.opends.guitools.controlpanel.util.Utilities;
047import org.opends.messages.AdminToolMessages;
048import org.forgerock.i18n.LocalizableMessage;
049import org.forgerock.opendj.ldap.DN;
050import org.opends.server.util.ServerConstants;
051
052/** The class that is in charge of adding a set of entries to a set of static groups. */
053public class AddToGroupTask extends Task
054{
055  private Set<String> backendSet;
056  private LinkedHashSet<DN> dns = new LinkedHashSet<>();
057  private LinkedHashSet<DN> groupDns = new LinkedHashSet<>();
058
059  /**
060   * Constructor of the task.
061   * @param info the control panel information.
062   * @param dlg the progress dialog where the task progress will be displayed.
063   * @param dns the DNs of the entries we want to add to the groups.
064   * @param groupDns the groups that we want to modify.
065   */
066  public AddToGroupTask(ControlPanelInfo info, ProgressDialog dlg,
067      Set<DN> dns, Set<DN> groupDns)
068  {
069    super(info, dlg);
070    backendSet = new HashSet<>();
071    this.dns.addAll(dns);
072    this.groupDns.addAll(groupDns);
073    for (DN groupDn : groupDns)
074    {
075      for (BackendDescriptor backend :
076        info.getServerDescriptor().getBackends())
077      {
078        for (BaseDNDescriptor baseDN : backend.getBaseDns())
079        {
080          if (groupDn.isSubordinateOrEqualTo(baseDN.getDn()))
081          {
082            backendSet.add(backend.getBackendID());
083          }
084        }
085      }
086    }
087  }
088
089  @Override
090  public Type getType()
091  {
092    return Type.MODIFY_ENTRY;
093  }
094
095  @Override
096  public Set<String> getBackends()
097  {
098    return backendSet;
099  }
100
101  @Override
102  public LocalizableMessage getTaskDescription()
103  {
104    return AdminToolMessages.INFO_CTRL_PANEL_ADD_TO_GROUP_TASK_DESCRIPTION.get();
105  }
106
107  @Override
108  protected String getCommandLinePath()
109  {
110    return null;
111  }
112
113  @Override
114  protected ArrayList<String> getCommandLineArguments()
115  {
116    return new ArrayList<>();
117  }
118
119  @Override
120  public boolean canLaunch(Task taskToBeLaunched,
121      Collection<LocalizableMessage> incompatibilityReasons)
122  {
123    if (!isServerRunning()
124        && state == State.RUNNING
125        && runningOnSameServer(taskToBeLaunched))
126    {
127      // All the operations are incompatible if they apply to this
128      // backend for safety.  This is a short operation so the limitation
129      // has not a lot of impact.
130      Set<String> backends = new TreeSet<>(taskToBeLaunched.getBackends());
131      backends.retainAll(getBackends());
132      if (!backends.isEmpty())
133      {
134        incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
135        return false;
136      }
137    }
138    return true;
139  }
140
141  @Override
142  public boolean regenerateDescriptor()
143  {
144    return false;
145  }
146
147  @Override
148  public void runTask()
149  {
150    state = State.RUNNING;
151    lastException = null;
152
153    try
154    {
155      for (final DN groupDn : groupDns)
156      {
157        final Collection<ModificationItem> modifications =
158          getModifications(groupDn, dns);
159        if (!modifications.isEmpty())
160        {
161          ModificationItem[] mods =
162          new ModificationItem[modifications.size()];
163          modifications.toArray(mods);
164
165          SwingUtilities.invokeLater(new Runnable()
166          {
167            @Override
168            public void run()
169            {
170              printEquivalentCommandToModify(groupDn, modifications, false);
171              getProgressDialog().appendProgressHtml(
172                  Utilities.getProgressWithPoints(
173                      INFO_CTRL_PANEL_ADDING_TO_GROUP.get(groupDn),
174                      ColorAndFontConstants.progressFont));
175            }
176          });
177
178          getInfo().getConnection().getLdapContext().modifyAttributes(
179              Utilities.getJNDIName(groupDn.toString()), mods);
180
181          SwingUtilities.invokeLater(new Runnable()
182          {
183            @Override
184            public void run()
185            {
186              getProgressDialog().appendProgressHtml(
187                  Utilities.getProgressDone(
188                      ColorAndFontConstants.progressFont));
189            }
190          });
191        }
192      }
193      state = State.FINISHED_SUCCESSFULLY;
194    }
195    catch (Throwable t)
196    {
197      lastException = t;
198      state = State.FINISHED_WITH_ERROR;
199    }
200  }
201
202  /**
203   * Returns the modifications that must be made to the provided group.
204   * @param groupDn the DN of the static group that must be updated.
205   * @param dns the list of entry DNs that must be added to the group.
206   * @return the list of modifications (in form of ModificationItem) that
207   *  must be made to the provided group.
208   * @throws NamingException if an error occurs.
209   */
210  private Collection<ModificationItem> getModifications(DN groupDn,
211  Set<DN> dns) throws NamingException
212  {
213    ArrayList<ModificationItem> modifications = new ArrayList<>();
214
215    // Search for the group entry
216    SearchControls ctls = new SearchControls();
217    ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
218    ctls.setReturningAttributes(
219        new String[] {
220            ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME,
221            ServerConstants.ATTR_MEMBER,
222            ServerConstants.ATTR_UNIQUE_MEMBER
223        });
224    String filter = BrowserController.ALL_OBJECTS_FILTER;
225    NamingEnumeration<SearchResult> result =
226      getInfo().getConnection().getLdapContext().search(
227          Utilities.getJNDIName(groupDn.toString()),
228          filter, ctls);
229
230    try
231    {
232      String memberAttr = ServerConstants.ATTR_MEMBER;
233      while (result.hasMore())
234      {
235        SearchResult sr = result.next();
236        Set<String> objectClasses =
237          ConnectionUtils.getValues(sr, ServerConstants
238            .OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
239        if (objectClasses.contains(ServerConstants.OC_GROUP_OF_UNIQUE_NAMES))
240        {
241          memberAttr = ServerConstants.ATTR_UNIQUE_MEMBER;
242        }
243        Set<String> values = ConnectionUtils.getValues(sr, memberAttr);
244        Set<String> dnsToAdd = new LinkedHashSet<>();
245        if (values != null)
246        {
247          for (DN newDn : dns)
248          {
249            boolean found = false;
250            for (String dn : values)
251            {
252              if (Utilities.areDnsEqual(dn, newDn.toString()))
253              {
254                found = true;
255                break;
256              }
257            }
258            if (!found)
259            {
260              dnsToAdd.add(newDn.toString());
261            }
262          }
263        }
264        else
265        {
266          for (DN newDn : dns)
267          {
268            dnsToAdd.add(newDn.toString());
269          }
270        }
271        if (!dnsToAdd.isEmpty())
272        {
273          Attribute attribute = new BasicAttribute(memberAttr);
274          for (String dn : dnsToAdd)
275          {
276            attribute.add(dn);
277          }
278          modifications.add(new ModificationItem(
279              DirContext.ADD_ATTRIBUTE,
280              attribute));
281        }
282      }
283    }
284    finally
285    {
286      result.close();
287    }
288    return modifications;
289  }
290}
291