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 2012-2015 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.ui;
018
019import static org.opends.messages.AdminToolMessages.*;
020
021import java.awt.Component;
022import java.awt.GridBagConstraints;
023import java.awt.Point;
024import java.io.IOException;
025import java.io.StringReader;
026import java.util.List;
027
028import javax.swing.JLabel;
029import javax.swing.JScrollPane;
030import javax.swing.JTextArea;
031import javax.swing.SwingUtilities;
032import javax.swing.event.DocumentEvent;
033import javax.swing.event.DocumentListener;
034import javax.swing.tree.TreePath;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
038import org.opends.guitools.controlpanel.task.OfflineUpdateException;
039import org.opends.guitools.controlpanel.util.Utilities;
040import org.opends.server.types.Entry;
041import org.opends.server.types.LDIFImportConfig;
042import org.opends.server.types.OpenDsException;
043import org.opends.server.util.Base64;
044import org.opends.server.util.LDIFReader;
045import org.opends.server.util.StaticUtils;
046
047/**
048 * The panel displaying an LDIF view of an entry.
049 */
050public class LDIFViewEntryPanel extends ViewEntryPanel
051{
052  /** Callback that sets the viewport's view position. */
053  private static final class SetViewPosition implements Runnable
054  {
055    private final Point p;
056    private final JScrollPane scroll;
057
058    private SetViewPosition(JScrollPane scroll, Point p)
059    {
060      this.p = p;
061      this.scroll = scroll;
062    }
063
064    /** {@inheritDoc} */
065    @Override
066    public void run()
067    {
068      if (p != null && scroll.getViewport().contains(p))
069      {
070        scroll.getViewport().setViewPosition(p);
071      }
072    }
073  }
074
075  private static final long serialVersionUID = 2775960608128921072L;
076  private JScrollPane editableScroll;
077  private JScrollPane readOnlyScroll;
078  private JTextArea editableAttributes;
079  private JTextArea readOnlyAttributes;
080  private CustomSearchResult searchResult;
081
082  /** Default constructor. */
083  public LDIFViewEntryPanel()
084  {
085    createLayout();
086  }
087
088  /** {@inheritDoc} */
089  @Override
090  public Component getPreferredFocusComponent()
091  {
092    return editableAttributes;
093  }
094
095  /**
096   * Creates the layout of the panel (but the contents are not populated here).
097   */
098  private void createLayout()
099  {
100    GridBagConstraints gbc = new GridBagConstraints();
101    gbc.gridx = 0;
102    gbc.gridy = 0;
103    gbc.gridwidth = 1;
104    gbc.fill = GridBagConstraints.NONE;
105    gbc.anchor = GridBagConstraints.WEST;
106    gbc.weightx = 1.0;
107
108    addTitlePanel(this, gbc);
109
110    gbc.gridy ++;
111    gbc.insets.top = 10;
112
113    editableAttributes = Utilities.createTextArea(LocalizableMessage.EMPTY, 20, 30);
114    editableAttributes.getDocument().addDocumentListener(new DocumentListener()
115    {
116      @Override
117      public void insertUpdate(DocumentEvent ev)
118      {
119        notifyListeners();
120      }
121
122      @Override
123      public void changedUpdate(DocumentEvent ev)
124      {
125        notifyListeners();
126      }
127
128      @Override
129      public void removeUpdate(DocumentEvent ev)
130      {
131        notifyListeners();
132      }
133    });
134    gbc.weighty = 0.6;
135    gbc.fill = GridBagConstraints.BOTH;
136    gbc.gridy ++;
137    editableScroll = Utilities.createScrollPane(editableAttributes);
138    add(editableScroll, gbc);
139
140
141    gbc.weighty = 0.0;
142    gbc.insets.top = 10;
143    JLabel lReadOnly = Utilities.createPrimaryLabel(
144        INFO_CTRL_PANEL_NON_EDITABLE_ATTRIBUTES.get());
145    gbc.gridy ++;
146    add(lReadOnly, gbc);
147    gbc.insets.top = 5;
148    readOnlyAttributes = Utilities.createNonEditableTextArea(LocalizableMessage.EMPTY, 10, 30);
149    gbc.weightx = 1.0;
150    gbc.weighty = 0.4;
151    gbc.fill = GridBagConstraints.BOTH;
152    gbc.gridy ++;
153    readOnlyScroll = Utilities.createScrollPane(readOnlyAttributes);
154    add(readOnlyScroll, gbc);
155  }
156
157  /** {@inheritDoc} */
158  @Override
159  public void update(CustomSearchResult sr, boolean isReadOnly, TreePath path)
160  {
161    boolean sameEntry = false;
162    if (searchResult != null && sr != null)
163    {
164      sameEntry = searchResult.getDN().equals(sr.getDN());
165    }
166
167    searchResult = sr;
168    updateTitle(sr, path);
169
170    StringBuilder sb = new StringBuilder();
171    sb.append("dn: ").append(sr.getDN());
172
173    if (isReadOnly)
174    {
175      editableScroll.setVisible(false);
176      for (String attrName : sr.getAttributeNames())
177      {
178        List<Object> values = sr.getAttributeValues(attrName);
179        for (Object o : values)
180        {
181          sb.append("\n").append(getLDIFLine(attrName, o));
182        }
183      }
184      final Point p1 = sameEntry ?
185          readOnlyScroll.getViewport().getViewPosition() : new Point(0, 0);
186      readOnlyAttributes.setText(sb.toString());
187      SwingUtilities.invokeLater(new SetViewPosition(readOnlyScroll, p1));
188    }
189    else
190    {
191      editableScroll.setVisible(true);
192
193      for (String attrName : sr.getAttributeNames())
194      {
195        if (!schemaReadOnlyAttributesLowerCase.contains(attrName.toLowerCase()))
196        {
197          List<Object> values = sr.getAttributeValues(attrName);
198          for (Object o : values)
199          {
200            sb.append("\n").append(getLDIFLine(attrName, o));
201          }
202        }
203      }
204      final Point p1 = sameEntry ?
205          editableScroll.getViewport().getViewPosition() : new Point(0, 0);
206      ignoreEntryChangeEvents = true;
207      editableAttributes.setText(sb.toString());
208      ignoreEntryChangeEvents = false;
209
210      SwingUtilities.invokeLater(new SetViewPosition(editableScroll, p1));
211      // Read-only attributes
212      boolean oneLineAdded = false;
213      sb = new StringBuilder();
214      for (String attrName : schemaReadOnlyAttributes)
215      {
216        List<Object> values = sr.getAttributeValues(attrName);
217        for (Object o : values)
218        {
219          if (oneLineAdded)
220          {
221            sb.append("\n");
222          }
223          oneLineAdded = true;
224          sb.append(getLDIFLine(attrName, o));
225        }
226      }
227      final Point p2 = sameEntry ?
228          readOnlyScroll.getViewport().getViewPosition() : new Point(0, 0);
229      readOnlyAttributes.setText(sb.toString());
230      SwingUtilities.invokeLater(new SetViewPosition(readOnlyScroll, p2));
231    }
232  }
233
234  /** {@inheritDoc} */
235  @Override
236  public GenericDialog.ButtonType getButtonType()
237  {
238    return GenericDialog.ButtonType.NO_BUTTON;
239  }
240
241
242  /** {@inheritDoc} */
243  @Override
244  protected String getDisplayedDN()
245  {
246    String dn = null;
247    // Do it fast, this is called to update the dn displayed in the title.
248    String ldif = getLDIF();
249    int index = ldif.toLowerCase().indexOf("dn: ");
250    if (index != -1)
251    {
252      int index2 = ldif.indexOf("\n", index);
253      if (index2 != -1)
254      {
255        dn = ldif.substring(index + 3, index2).trim();
256      }
257    }
258    return dn;
259  }
260
261  /** {@inheritDoc} */
262  @Override
263  protected List<Object> getValues(String attrName)
264  {
265    throw new IllegalStateException("This method should not be called.");
266  }
267
268  /** {@inheritDoc} */
269  @Override
270  public Entry getEntry() throws OpenDsException
271  {
272    LDIFImportConfig ldifImportConfig = null;
273    try
274    {
275      String ldif = getLDIF();
276
277      ldifImportConfig = new LDIFImportConfig(new StringReader(ldif));
278      LDIFReader reader = new LDIFReader(ldifImportConfig);
279      Entry entry = reader.readEntry(checkSchema());
280      addValuesInRDN(entry);
281      return entry;
282    }
283    catch (IOException ioe)
284    {
285      throw new OfflineUpdateException(
286          ERR_CTRL_PANEL_ERROR_CHECKING_ENTRY.get(ioe), ioe);
287    }
288    finally
289    {
290      if (ldifImportConfig != null)
291      {
292        ldifImportConfig.close();
293      }
294    }
295  }
296
297  /**
298   * Returns the LDIF representation of the entry, only returns the editable
299   * attributes.
300   * @return the LDIF representation of the entry.
301   */
302  private String getLDIF()
303  {
304    return editableAttributes.getText();
305  }
306
307  /**
308   * Returns the equivalent LDIF line for a given attribute and value.
309   * @param attrName the attribute name.
310   * @param o the value.
311   * @return the equivalent LDIF line for the provided attribute and value.
312   */
313  private String getLDIFLine(String attrName, Object o)
314  {
315    String attrValue;
316    if (o instanceof String)
317    {
318      if (Utilities.hasControlCharaters((String)o))
319      {
320        attrValue = Base64.encode(StaticUtils.getBytes((String)o));
321        attrName = attrName+":";
322      }
323      else
324      {
325        attrValue = (String)o;
326      }
327    }
328    else if (o instanceof byte[])
329    {
330      attrValue = Base64.encode((byte[])o);
331      // To indicate that is base64 encoded
332      attrName = attrName+":";
333    }
334    else
335    {
336      attrValue = String.valueOf(o);
337    }
338
339    return attrName+": "+ attrValue;
340  }
341}