001/*
002 * Copyright 2009-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.unboundidds.monitors;
022
023
024
025import java.util.Collections;
026import java.util.LinkedHashMap;
027import java.util.Map;
028
029import com.unboundid.ldap.sdk.Entry;
030import com.unboundid.util.Debug;
031import com.unboundid.util.NotMutable;
032import com.unboundid.util.StaticUtils;
033import com.unboundid.util.ThreadSafety;
034import com.unboundid.util.ThreadSafetyLevel;
035
036import static com.unboundid.ldap.sdk.unboundidds.monitors.MonitorMessages.*;
037
038
039
040/**
041 * This class defines a monitor entry that provides information about the state
042 * of a replica, including the base DN, replica ID, and generation ID, as well
043 * as information about its communication with the replication server
044 * <BR>
045 * <BLOCKQUOTE>
046 *   <B>NOTE:</B>  This class, and other classes within the
047 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
048 *   supported for use against Ping Identity, UnboundID, and
049 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
050 *   for proprietary functionality or for external specifications that are not
051 *   considered stable or mature enough to be guaranteed to work in an
052 *   interoperable way with other types of LDAP servers.
053 * </BLOCKQUOTE>
054 * <BR>
055 * The server should present a replica monitor entry for each replicated base
056 * DN.  They can be retrieved using the
057 * {@link MonitorManager#getReplicaMonitorEntries} method.  These entries
058 * provide specific methods for accessing information about the replica.
059 * Alternately, this information may be accessed using the generic API.  See the
060 * {@link MonitorManager} class documentation for an example that demonstrates
061 * the use of the generic API for accessing monitor data.
062 */
063@NotMutable()
064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065public final class ReplicaMonitorEntry
066       extends MonitorEntry
067{
068  /**
069   * The structural object class used in replica monitor entries.
070   */
071  static final String REPLICA_MONITOR_OC =
072       "ds-replica-monitor-entry";
073
074
075
076  /**
077   * The name of the attribute that contains the base DNs for the replicated
078   * data.
079   */
080  private static final String ATTR_BASE_DN = "base-dn";
081
082
083
084  /**
085   * The name of the attribute that contains the address and port of the
086   * replication server to which the replica is connected.
087   */
088  private static final String ATTR_CONNECTED_TO =
089       "connected-to";
090
091
092
093  /**
094   * The name of the attribute that provides information about the current
095   * receive window size.
096   */
097  private static final String ATTR_CURRENT_RECEIVE_WINDOW_SIZE =
098       "current-rcv-window";
099
100
101
102  /**
103   * The name of the attribute that provides information about the current send
104   * window size.
105   */
106  private static final String ATTR_CURRENT_SEND_WINDOW_SIZE =
107       "current-send-window";
108
109
110
111  /**
112   * The name of the attribute that provides the generation ID for the replica.
113   */
114  private static final String ATTR_GENERATION_ID = "generation-id";
115
116
117
118  /**
119   * The name of the attribute that provides information about the number of
120   * times the connection to the replication server has been lost.
121   */
122  private static final String ATTR_LOST_CONNECTIONS = "lost-connections";
123
124
125
126  /**
127   * The name of the attribute that provides information about the maximum
128   * receive window size.
129   */
130  private static final String ATTR_MAX_RECEIVE_WINDOW_SIZE =
131       "max-rcv-window";
132
133
134
135  /**
136   * The name of the attribute that provides information about the maximum send
137   * window size.
138   */
139  private static final String ATTR_MAX_SEND_WINDOW_SIZE =
140       "max-send-window";
141
142
143
144  /**
145   * The name of the attribute that provides information about the number of
146   * pending updates which are currently being processed by the Directory Server
147   * and have not yet been sent to the replication server.
148   */
149  private static final String ATTR_PENDING_UPDATES = "pending-updates";
150
151
152
153  /**
154   * The name of the attribute that provides information about the number of
155   * updates received from the replication server for this replica.
156   */
157  private static final String ATTR_RECEIVED_UPDATES = "received-updates";
158
159
160
161  /**
162   * The name of the attribute that provides the replica ID for this replica.
163   */
164  private static final String ATTR_REPLICA_ID = "replica-id";
165
166
167
168  /**
169   * The name of the attribute that provides information about the number of
170   * updates that were replayed after resolving a modify conflict.
171   */
172  private static final String ATTR_RESOLVED_MODIFY_CONFLICTS =
173       "resolved-modify-conflicts";
174
175
176
177  /**
178   * The name of the attribute that provides information about the number of
179   * updates that were replayed after resolving a naming conflict.
180   */
181  private static final String ATTR_RESOLVED_NAMING_CONFLICTS =
182       "resolved-naming-conflicts";
183
184
185
186  /**
187   * The name of the attribute that provides information about the number of
188   * updates sent to the replication server from this replica.
189   */
190  private static final String ATTR_SENT_UPDATES = "sent-updates";
191
192
193
194  /**
195   * The name of the attribute that indicates whether SSL is used when
196   * communicating with the replication server.
197   */
198  private static final String ATTR_SSL_ENCRYPTION = "ssl-encryption";
199
200
201
202  /**
203   * The name of the attribute that provides information about the number of
204   * updates that have been successfully replayed with no problems.
205   */
206  private static final String ATTR_SUCCESSFUL_REPLAYED = "replayed-updates-ok";
207
208
209
210  /**
211   * The name of the attribute that provides information about the total number
212   * of updates that have been replayed in some form.
213   */
214  private static final String ATTR_TOTAL_REPLAYED = "replayed-updates";
215
216
217
218  /**
219   * The name of the attribute that provides information about the number of
220   * updates that could not be replayed because of an unresolved naming
221   * conflict.
222   */
223  private static final String ATTR_UNRESOLVED_NAMING_CONFLICTS =
224       "unresolved-naming-conflicts";
225
226
227
228  /**
229   * The serial version UID for this serializable class.
230   */
231  private static final long serialVersionUID = -9164207693317460579L;
232
233
234
235  // Indicates whether the replica uses SSL when communicating with the
236  // replication server.
237  private final Boolean useSSL;
238
239  // The current receive window size.
240  private final Long currentReceiveWindowSize;
241
242  // The current send window size.
243  private final Long currentSendWindowSize;
244
245  // The number of lost connections.
246  private final Long lostConnections;
247
248  // The maximum receive window size.
249  private final Long maxReceiveWindowSize;
250
251  // The maximum send window size.
252  private final Long maxSendWindowSize;
253
254  // The number of pending updates that haven't been sent to the replication
255  // server.
256  private final Long pendingUpdates;
257
258  // The number of updates received from the replication server.
259  private final Long receivedUpdates;
260
261  // The number of updates replayed after resolving a modify conflict.
262  private final Long replayedAfterModifyConflict;
263
264  // The number of updates replayed after resolving a naming conflict.
265  private final Long replayedAfterNamingConflict;
266
267  // The port number of the replication server.
268  private final Long replicationServerPort;
269
270  // The number of updates sent to the replication server.
271  private final Long sentUpdates;
272
273  // The number of updates replayed successfully.
274  private final Long successfullyReplayed;
275
276  // The total number of updates replayed.
277  private final Long totalReplayed;
278
279  // The number of unresolved naming conflicts that could not be successfully
280  // replayed.
281  private final Long unresolvedNamingConflicts;
282
283  // The base DN for the replicated data.
284  private final String baseDN;
285
286  // The generation ID for the replicated data.
287  private final String generationID;
288
289  // The replica ID for the replica.
290  private final String replicaID;
291
292  // The address of the replication server.
293  private final String replicationServerAddress;
294
295
296
297  /**
298   * Creates a new replica monitor entry from the provided entry.
299   *
300   * @param  entry  The entry to be parsed as a replica monitor entry.  It must
301   *                not be {@code null}.
302   */
303  public ReplicaMonitorEntry(final Entry entry)
304  {
305    super(entry);
306
307    useSSL                      = getBoolean(ATTR_SSL_ENCRYPTION);
308    lostConnections             = getLong(ATTR_LOST_CONNECTIONS);
309    receivedUpdates             = getLong(ATTR_RECEIVED_UPDATES);
310    sentUpdates                 = getLong(ATTR_SENT_UPDATES);
311    pendingUpdates              = getLong(ATTR_PENDING_UPDATES);
312    totalReplayed               = getLong(ATTR_TOTAL_REPLAYED);
313    successfullyReplayed        = getLong(ATTR_SUCCESSFUL_REPLAYED);
314    replayedAfterModifyConflict = getLong(ATTR_RESOLVED_MODIFY_CONFLICTS);
315    replayedAfterNamingConflict = getLong(ATTR_RESOLVED_NAMING_CONFLICTS);
316    unresolvedNamingConflicts   = getLong(ATTR_UNRESOLVED_NAMING_CONFLICTS);
317    currentReceiveWindowSize    = getLong(ATTR_CURRENT_RECEIVE_WINDOW_SIZE);
318    currentSendWindowSize       = getLong(ATTR_CURRENT_SEND_WINDOW_SIZE);
319    maxReceiveWindowSize        = getLong(ATTR_MAX_RECEIVE_WINDOW_SIZE);
320    maxSendWindowSize           = getLong(ATTR_MAX_SEND_WINDOW_SIZE);
321    baseDN                      = getString(ATTR_BASE_DN);
322    generationID                = getString(ATTR_GENERATION_ID);
323    replicaID                   = getString(ATTR_REPLICA_ID);
324
325    String addr = null;
326    Long   port = null;
327    final String connectedTo = getString(ATTR_CONNECTED_TO);
328    if (connectedTo != null)
329    {
330      try
331      {
332        final int colonPos = connectedTo.indexOf(':');
333        if (colonPos > 0)
334        {
335          addr = connectedTo.substring(0, colonPos);
336          port = Long.parseLong(connectedTo.substring(colonPos+1));
337        }
338      }
339      catch (final Exception e)
340      {
341        Debug.debugException(e);
342        addr = null;
343        port = null;
344      }
345    }
346
347    replicationServerAddress = addr;
348    replicationServerPort    = port;
349  }
350
351
352
353  /**
354   * Retrieves the base DN for this replica.
355   *
356   * @return  The base DN for this replica, or {@code null} if it was not
357   *          included in the monitor entry.
358   */
359  public String getBaseDN()
360  {
361    return baseDN;
362  }
363
364
365
366  /**
367   * Retrieves the replica ID for this replica.
368   *
369   * @return  The replica ID for this replica, or {@code null} if it was not
370   *          included in the monitor entry.
371   */
372  public String getReplicaID()
373  {
374    return replicaID;
375  }
376
377
378
379  /**
380   * Retrieves the generation ID for this replica.
381   *
382   * @return  The generation ID for this replica, or {@code null} if it was not
383   *          included in the monitor entry.
384   */
385  public String getGenerationID()
386  {
387    return generationID;
388  }
389
390
391
392  /**
393   * Retrieves the address of the replication server to which this replica is
394   * connected.
395   *
396   * @return  The address of the replication server to which this replica is
397   *          connected, or {@code null} if it was not included in the monitor
398   *          entry.
399   */
400  public String getReplicationServerAddress()
401  {
402    return replicationServerAddress;
403  }
404
405
406
407  /**
408   * Retrieves the port number of the replication server to which this replica
409   * is connected.
410   *
411   * @return  The port number of the replication server to which this replica is
412   *          connected, or {@code null} if it was not included in the monitor
413   *          entry.
414   */
415  public Long getReplicationServerPort()
416  {
417    return replicationServerPort;
418  }
419
420
421
422  /**
423   * Indicates whether this replica uses SSL when communicating with the
424   * replication server.
425   *
426   * @return  {@code Boolean.TRUE} if this replica uses SSL when communicating
427   *          with the replication server, {@code Boolean.FALSE} if it does not
428   *          use SSL, or {@code null} if it was not included in the monitor
429   *          entry.
430   */
431  public Boolean useSSL()
432  {
433    return useSSL;
434  }
435
436
437
438  /**
439   * Retrieves the number of times this replica has lost the connection to a
440   * replication server.
441   *
442   * @return  The number of times this replica has lost the connection to a
443   *          replication server, or {@code null} if it was not included in the
444   *          monitor entry.
445   */
446  public Long getLostConnections()
447  {
448    return lostConnections;
449  }
450
451
452
453  /**
454   * Retrieves the number of updates that this replica has received from the
455   * replication server.
456   *
457   * @return  The number of updates that this replica has received from the
458   *          replication server, or {@code null} if it was not included in the
459   *          monitor entry.
460   */
461  public Long getReceivedUpdates()
462  {
463    return receivedUpdates;
464  }
465
466
467
468  /**
469   * Retrieves the number of updates that this replica has sent to the
470   * replication server.
471   *
472   * @return  The number of updates that this replica has sent to the
473   *          replication server, or {@code null} if it was not included in the
474   *          monitor entry.
475   */
476  public Long getSentUpdates()
477  {
478    return sentUpdates;
479  }
480
481
482
483  /**
484   * Retrieves the number of updates that are currently in progress in the
485   * Directory Server and have not yet been sent to the replication server.
486   *
487   * @return  The number of updates that are currently in progress in the
488   *          Directory Server and have not yet been sent to the replication
489   *          server, or {@code null} if it was not included in the monitor
490   *          entry.
491   */
492  public Long getPendingUpdates()
493  {
494    return pendingUpdates;
495  }
496
497
498
499  /**
500   * Retrieves the total number of updates that have been replayed in this
501   * replica.
502   *
503   * @return  The total number of updates that have been replayed in this
504   *          replica, or {@code null} if it was not included in the monitor
505   *          entry.
506   */
507  public Long getTotalUpdatesReplayed()
508  {
509    return totalReplayed;
510  }
511
512
513
514  /**
515   * Retrieves the number of updates that have been successfully replayed in
516   * this replica without conflicts.
517   *
518   * @return  The number of updates that have been successfully replayed in this
519   *          replica without conflicts, or {@code null} if it was not included
520   *          in the monitor entry.
521   */
522  public Long getUpdatesSuccessfullyReplayed()
523  {
524    return successfullyReplayed;
525  }
526
527
528
529  /**
530   * Retrieves the number of updates that have been replayed in this replica
531   * after automatically resolving a modify conflict.
532   *
533   * @return  The number of updates that have been replayed in this replica
534   *          after automatically resolving a modify conflict, or {@code null}
535   *          if it was not included in the monitor entry.
536   */
537  public Long getUpdatesReplayedAfterModifyConflict()
538  {
539    return replayedAfterModifyConflict;
540  }
541
542
543
544  /**
545   * Retrieves the number of updates that have been replayed in this replica
546   * after automatically resolving a naming conflict.
547   *
548   * @return  The number of updates that have been replayed in this replica
549   *          after automatically resolving a naming conflict, or {@code null}
550   *          if it was not included in the monitor entry.
551   */
552  public Long getUpdatesReplayedAfterNamingConflict()
553  {
554    return replayedAfterNamingConflict;
555  }
556
557
558
559  /**
560   * Retrieves the number of updates that could not be replayed as a result of a
561   * naming conflict that could not be automatically resolved.
562   *
563   * @return  The number of updates that could not be replayed as a result of a
564   *          naming conflict that could not be automatically resolved, or
565   *          {@code null} if it was not included in the monitor entry.
566   */
567  public Long getUnresolvedNamingConflicts()
568  {
569    return unresolvedNamingConflicts;
570  }
571
572
573
574  /**
575   * Retrieves the current receive window size for this replica.
576   *
577   * @return  The current receive window size for this replica, or {@code null}
578   *          if it was not included in the monitor entry.
579   */
580  public Long getCurrentReceiveWindowSize()
581  {
582    return currentReceiveWindowSize;
583  }
584
585
586
587  /**
588   * Retrieves the current send window size for this replica.
589   *
590   * @return  The current send window size for this replica, or {@code null} if
591   *          it was not included in the monitor entry.
592   */
593  public Long getCurrentSendWindowSize()
594  {
595    return currentSendWindowSize;
596  }
597
598
599
600  /**
601   * Retrieves the maximum receive window size for this replica.
602   *
603   * @return  The maximum receive window size for this replica, or {@code null}
604   *          if it was not included in the monitor entry.
605   */
606  public Long getMaximumReceiveWindowSize()
607  {
608    return maxReceiveWindowSize;
609  }
610
611
612
613  /**
614   * Retrieves the maximum send window size for this replica.
615   *
616   * @return  The maximum send window size for this replica, or {@code null} if
617   *          it was not included in the monitor entry.
618   */
619  public Long getMaximumSendWindowSize()
620  {
621    return maxSendWindowSize;
622  }
623
624
625
626  /**
627   * {@inheritDoc}
628   */
629  @Override()
630  public String getMonitorDisplayName()
631  {
632    return INFO_REPLICA_MONITOR_DISPNAME.get();
633  }
634
635
636
637  /**
638   * {@inheritDoc}
639   */
640  @Override()
641  public String getMonitorDescription()
642  {
643    return INFO_REPLICA_MONITOR_DESC.get();
644  }
645
646
647
648  /**
649   * {@inheritDoc}
650   */
651  @Override()
652  public Map<String,MonitorAttribute> getMonitorAttributes()
653  {
654    final LinkedHashMap<String,MonitorAttribute> attrs =
655         new LinkedHashMap<>(StaticUtils.computeMapCapacity(30));
656
657    if (baseDN != null)
658    {
659      addMonitorAttribute(attrs,
660           ATTR_BASE_DN,
661           INFO_REPLICA_DISPNAME_BASE_DN.get(),
662           INFO_REPLICA_DESC_BASE_DN.get(),
663           baseDN);
664    }
665
666    if (replicaID != null)
667    {
668      addMonitorAttribute(attrs,
669           ATTR_REPLICA_ID,
670           INFO_REPLICA_DISPNAME_REPLICA_ID.get(),
671           INFO_REPLICA_DESC_REPLICA_ID.get(),
672           replicaID);
673    }
674
675    if (generationID != null)
676    {
677      addMonitorAttribute(attrs,
678           ATTR_GENERATION_ID,
679           INFO_REPLICA_DISPNAME_GENERATION_ID.get(),
680           INFO_REPLICA_DESC_GENERATION_ID.get(),
681           generationID);
682    }
683
684    if (replicationServerAddress != null)
685    {
686      addMonitorAttribute(attrs,
687           ATTR_CONNECTED_TO,
688           INFO_REPLICA_DISPNAME_CONNECTED_TO.get(),
689           INFO_REPLICA_DESC_CONNECTED_TO.get(),
690           replicationServerAddress + ':' + replicationServerPort);
691    }
692
693    if (useSSL != null)
694    {
695      addMonitorAttribute(attrs,
696           ATTR_SSL_ENCRYPTION,
697           INFO_REPLICA_DISPNAME_USE_SSL.get(),
698           INFO_REPLICA_DESC_USE_SSL.get(),
699           useSSL);
700    }
701
702    if (lostConnections != null)
703    {
704      addMonitorAttribute(attrs,
705           ATTR_LOST_CONNECTIONS,
706           INFO_REPLICA_DISPNAME_LOST_CONNECTIONS.get(),
707           INFO_REPLICA_DESC_LOST_CONNECTIONS.get(),
708           lostConnections);
709    }
710
711    if (receivedUpdates != null)
712    {
713      addMonitorAttribute(attrs,
714           ATTR_RECEIVED_UPDATES,
715           INFO_REPLICA_DISPNAME_RECEIVED_UPDATES.get(),
716           INFO_REPLICA_DESC_RECEIVED_UPDATES.get(),
717           receivedUpdates);
718    }
719
720    if (sentUpdates != null)
721    {
722      addMonitorAttribute(attrs,
723           ATTR_SENT_UPDATES,
724           INFO_REPLICA_DISPNAME_SENT_UPDATES.get(),
725           INFO_REPLICA_DESC_SENT_UPDATES.get(),
726           sentUpdates);
727    }
728
729    if (pendingUpdates != null)
730    {
731      addMonitorAttribute(attrs,
732           ATTR_PENDING_UPDATES,
733           INFO_REPLICA_DISPNAME_PENDING_UPDATES.get(),
734           INFO_REPLICA_DESC_PENDING_UPDATES.get(),
735           pendingUpdates);
736    }
737
738    if (totalReplayed != null)
739    {
740      addMonitorAttribute(attrs,
741           ATTR_TOTAL_REPLAYED,
742           INFO_REPLICA_DISPNAME_TOTAL_REPLAYED.get(),
743           INFO_REPLICA_DESC_TOTAL_REPLAYED.get(),
744           totalReplayed);
745    }
746
747    if (successfullyReplayed != null)
748    {
749      addMonitorAttribute(attrs,
750           ATTR_SUCCESSFUL_REPLAYED,
751           INFO_REPLICA_DISPNAME_SUCCESSFUL_REPLAYED.get(),
752           INFO_REPLICA_DESC_SUCCESSFUL_REPLAYED.get(),
753           successfullyReplayed);
754    }
755
756    if (replayedAfterModifyConflict != null)
757    {
758      addMonitorAttribute(attrs,
759           ATTR_RESOLVED_MODIFY_CONFLICTS,
760           INFO_REPLICA_DISPNAME_RESOLVED_MODIFY_CONFLICTS.get(),
761           INFO_REPLICA_DESC_RESOLVED_MODIFY_CONFLICTS.get(),
762           replayedAfterModifyConflict);
763    }
764
765    if (replayedAfterNamingConflict != null)
766    {
767      addMonitorAttribute(attrs,
768           ATTR_RESOLVED_NAMING_CONFLICTS,
769           INFO_REPLICA_DISPNAME_RESOLVED_NAMING_CONFLICTS.get(),
770           INFO_REPLICA_DESC_RESOLVED_NAMING_CONFLICTS.get(),
771           replayedAfterNamingConflict);
772    }
773
774    if (unresolvedNamingConflicts != null)
775    {
776      addMonitorAttribute(attrs,
777           ATTR_UNRESOLVED_NAMING_CONFLICTS,
778           INFO_REPLICA_DISPNAME_UNRESOLVED_NAMING_CONFLICTS.get(),
779           INFO_REPLICA_DESC_UNRESOLVED_NAMING_CONFLICTS.get(),
780           unresolvedNamingConflicts);
781    }
782
783    if (currentReceiveWindowSize != null)
784    {
785      addMonitorAttribute(attrs,
786           ATTR_CURRENT_RECEIVE_WINDOW_SIZE,
787           INFO_REPLICA_DISPNAME_CURRENT_RECEIVE_WINDOW_SIZE.get(),
788           INFO_REPLICA_DESC_CURRENT_RECEIVE_WINDOW_SIZE.get(),
789           currentReceiveWindowSize);
790    }
791
792    if (currentSendWindowSize != null)
793    {
794      addMonitorAttribute(attrs,
795           ATTR_CURRENT_SEND_WINDOW_SIZE,
796           INFO_REPLICA_DISPNAME_CURRENT_SEND_WINDOW_SIZE.get(),
797           INFO_REPLICA_DESC_CURRENT_SEND_WINDOW_SIZE.get(),
798           currentSendWindowSize);
799    }
800
801    if (maxReceiveWindowSize != null)
802    {
803      addMonitorAttribute(attrs,
804           ATTR_MAX_RECEIVE_WINDOW_SIZE,
805           INFO_REPLICA_DISPNAME_MAX_RECEIVE_WINDOW_SIZE.get(),
806           INFO_REPLICA_DESC_MAX_RECEIVE_WINDOW_SIZE.get(),
807           maxReceiveWindowSize);
808    }
809
810    if (maxSendWindowSize != null)
811    {
812      addMonitorAttribute(attrs,
813           ATTR_MAX_SEND_WINDOW_SIZE,
814           INFO_REPLICA_DISPNAME_MAX_SEND_WINDOW_SIZE.get(),
815           INFO_REPLICA_DESC_MAX_SEND_WINDOW_SIZE.get(),
816           maxSendWindowSize);
817    }
818
819    return Collections.unmodifiableMap(attrs);
820  }
821}