001    /*
002     * The MIT License
003     * Copyright (c) 2012 Microsoft Corporation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a copy
006     * of this software and associated documentation files (the "Software"), to deal
007     * in the Software without restriction, including without limitation the rights
008     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009     * copies of the Software, and to permit persons to whom the Software is
010     * furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021     * THE SOFTWARE.
022     */
023    
024    package microsoft.exchange.webservices.data.property.complex;
025    
026    import microsoft.exchange.webservices.data.attribute.EditorBrowsable;
027    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028    import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
029    import microsoft.exchange.webservices.data.core.ICustomXmlUpdateSerializer;
030    import microsoft.exchange.webservices.data.core.XmlAttributeNames;
031    import microsoft.exchange.webservices.data.core.XmlElementNames;
032    import microsoft.exchange.webservices.data.core.service.ServiceObject;
033    import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
034    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
035    import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
036    
037    import java.util.ArrayList;
038    import java.util.HashMap;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.Map.Entry;
042    
043    /**
044     * Represents a generic dictionary that can be sent to or retrieved from EWS.
045     * TKey The type of key. TEntry The type of entry.
046     *
047     * @param <TKey>   the generic type
048     * @param <TEntry> the generic type
049     */
050    @EditorBrowsable(state = EditorBrowsableState.Never)
051    public abstract class DictionaryProperty
052        <TKey, TEntry extends DictionaryEntryProperty<TKey>>
053        extends ComplexProperty implements ICustomXmlUpdateSerializer, IComplexPropertyChangedDelegate<TEntry> {
054    
055      /**
056       * The entries.
057       */
058      private Map<TKey, TEntry> entries = new HashMap<TKey, TEntry>();
059    
060      /**
061       * The removed entries.
062       */
063      private Map<TKey, TEntry> removedEntries = new HashMap<TKey, TEntry>();
064    
065      /**
066       * The added entries.
067       */
068      private List<TKey> addedEntries = new ArrayList<TKey>();
069    
070      /**
071       * The modified entries.
072       */
073      private List<TKey> modifiedEntries = new ArrayList<TKey>();
074    
075      /**
076       * Entry was changed.
077       *
078       * @param complexProperty the complex property
079       */
080      private void entryChanged(final TEntry complexProperty) {
081        TKey key = complexProperty.getKey();
082    
083        if (!this.addedEntries.contains(key) && !this.modifiedEntries.contains(key)) {
084          this.modifiedEntries.add(key);
085          this.changed();
086        }
087      }
088    
089      /**
090       * Writes the URI to XML.
091       *
092       * @param writer the writer
093       * @param key    the key
094       * @throws Exception the exception
095       */
096      private void writeUriToXml(EwsServiceXmlWriter writer, TKey key)
097          throws Exception {
098        writer.writeStartElement(XmlNamespace.Types,
099            XmlElementNames.IndexedFieldURI);
100        writer.writeAttributeValue(XmlAttributeNames.FieldURI, this
101            .getFieldURI());
102        writer.writeAttributeValue(XmlAttributeNames.FieldIndex, this
103            .getFieldIndex(key));
104        writer.writeEndElement();
105      }
106    
107      /**
108       * Gets the index of the field.
109       *
110       * @param key the key
111       * @return Key index.
112       */
113      protected String getFieldIndex(TKey key) {
114        return key.toString();
115      }
116    
117      /**
118       * Gets the field URI.
119       *
120       * @return Field URI.
121       */
122      protected String getFieldURI() {
123        return null;
124      }
125    
126      /**
127       * Creates the entry.
128       *
129       * @param reader the reader
130       * @return Dictionary entry.
131       */
132      protected TEntry createEntry(EwsServiceXmlReader reader) {
133        if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Entry)) {
134          return this.createEntryInstance();
135        } else {
136          return null;
137        }
138      }
139    
140      /**
141       * Creates instance of dictionary entry.
142       *
143       * @return New instance.
144       */
145      protected abstract TEntry createEntryInstance();
146    
147      /**
148       * Gets the name of the entry XML element.
149       *
150       * @param entry the entry
151       * @return XML element name.
152       */
153      protected String getEntryXmlElementName(TEntry entry) {
154        return XmlElementNames.Entry;
155      }
156    
157      /**
158       * Clears the change log.
159       */
160      public void clearChangeLog() {
161        this.addedEntries.clear();
162        this.removedEntries.clear();
163        this.modifiedEntries.clear();
164    
165        for (TEntry entry : this.entries.values()) {
166          entry.clearChangeLog();
167        }
168      }
169    
170      /**
171       * Add entry.
172       *
173       * @param entry the entry
174       */
175      protected void internalAdd(TEntry entry) {
176        entry.addOnChangeEvent(this);
177    
178        this.entries.put(entry.getKey(), entry);
179        this.addedEntries.add(entry.getKey());
180        this.removedEntries.remove(entry.getKey());
181    
182        this.changed();
183      }
184    
185      /**
186       * Complex property changed.
187       *
188       * @param complexProperty accepts ComplexProperty
189       */
190      @Override
191      public void complexPropertyChanged(final TEntry complexProperty) {
192        entryChanged(complexProperty);
193      }
194    
195      /**
196       * Add or replace entry.
197       *
198       * @param entry the entry
199       */
200      protected void internalAddOrReplace(TEntry entry) {
201        TEntry oldEntry;
202        if (this.entries.containsKey(entry.getKey())) {
203          oldEntry = this.entries.get(entry.getKey());
204          oldEntry.removeChangeEvent(this);
205    
206          entry.addOnChangeEvent(this);
207    
208          if (!this.addedEntries.contains(entry.getKey())) {
209            if (!this.modifiedEntries.contains(entry.getKey())) {
210              this.modifiedEntries.add(entry.getKey());
211            }
212          }
213    
214          this.changed();
215        } else {
216          this.internalAdd(entry);
217        }
218      }
219    
220      /**
221       * Remove entry based on key.
222       *
223       * @param key the key
224       */
225      protected void internalRemove(TKey key) {
226        TEntry entry;
227        if (this.entries.containsKey(key)) {
228          entry = this.entries.get(key);
229          entry.removeChangeEvent(this);
230    
231          this.entries.remove(key);
232          this.removedEntries.put(key, entry);
233    
234          this.changed();
235        }
236    
237        this.addedEntries.remove(key);
238      }
239    
240      /**
241       * Loads from XML.
242       *
243       * @param reader           the reader
244       * @param localElementName the local element name
245       * @throws Exception the exception
246       */
247      public void loadFromXml(EwsServiceXmlReader reader, String localElementName) throws Exception {
248        reader.ensureCurrentNodeIsStartElement(XmlNamespace.Types,
249            localElementName);
250    
251        if (!reader.isEmptyElement()) {
252          do {
253            reader.read();
254    
255            if (reader.isStartElement()) {
256              TEntry entry = this.createEntry(reader);
257    
258              if (entry != null) {
259                entry.loadFromXml(reader, reader.getLocalName());
260                this.internalAdd(entry);
261              } else {
262                reader.skipCurrentElement();
263              }
264            }
265          } while (!reader.isEndElement(XmlNamespace.Types,
266              localElementName));
267        } else {
268          reader.read();
269        }
270      }
271    
272      /**
273       * Writes to XML.
274       *
275       * @param writer         The writer
276       * @param xmlNamespace   The XML namespace.
277       * @param xmlElementName Name of the XML element.
278       * @throws Exception
279       */
280      @Override public void writeToXml(EwsServiceXmlWriter writer, XmlNamespace xmlNamespace,
281          String xmlElementName) throws Exception {
282        //  Only write collection if it has at least one element.
283        if (this.entries.size() > 0) {
284          super.writeToXml(
285              writer,
286              xmlNamespace,
287              xmlElementName);
288        }
289      }
290    
291      /**
292       * Writes elements to XML.
293       *
294       * @param writer the writer
295       * @throws Exception the exception
296       */
297      public void writeElementsToXml(EwsServiceXmlWriter writer)
298          throws Exception {
299        for (Entry<TKey, TEntry> keyValuePair : this.entries.entrySet()) {
300          keyValuePair.getValue().writeToXml(writer,
301              this.getEntryXmlElementName(keyValuePair.getValue()));
302        }
303      }
304    
305      /**
306       * Gets the entries.
307       *
308       * @return The entries.
309       */
310      protected Map<TKey, TEntry> getEntries() {
311        return entries;
312      }
313    
314      /**
315       * Determines whether this instance contains the specified key.
316       *
317       * @param key the key
318       * @return true if this instance contains the specified key; otherwise,
319       * false.
320       */
321      public boolean contains(TKey key) {
322        return this.entries.containsKey(key);
323      }
324    
325      /**
326       * Writes updates to XML.
327       *
328       * @param writer             the writer
329       * @param ewsObject          the ews object
330       * @param propertyDefinition the property definition
331       * @return True if property generated serialization.
332       * @throws Exception the exception
333       */
334      public boolean writeSetUpdateToXml(EwsServiceXmlWriter writer,
335          ServiceObject ewsObject, PropertyDefinition propertyDefinition)
336          throws Exception {
337        List<TEntry> tempEntries = new ArrayList<TEntry>();
338    
339        for (TKey key : this.addedEntries) {
340          tempEntries.add(this.entries.get(key));
341        }
342        for (TKey key : this.modifiedEntries) {
343          tempEntries.add(this.entries.get(key));
344        }
345        for (TEntry entry : tempEntries) {
346    
347          if (!entry.writeSetUpdateToXml(writer, ewsObject,
348              propertyDefinition.getXmlElement())) {
349            writer.writeStartElement(XmlNamespace.Types, ewsObject
350                .getSetFieldXmlElementName());
351            this.writeUriToXml(writer, entry.getKey());
352    
353            writer.writeStartElement(XmlNamespace.Types, ewsObject
354                .getXmlElementName());
355            //writer.writeStartElement(XmlNamespace.Types, propertyDefinition.getXmlElementName());
356            writer.writeStartElement(XmlNamespace.Types, propertyDefinition.getXmlElement());
357            entry.writeToXml(writer, this.getEntryXmlElementName(entry));
358            writer.writeEndElement();
359            writer.writeEndElement();
360    
361            writer.writeEndElement();
362          }
363        }
364    
365        for (TEntry entry : this.removedEntries.values()) {
366          if (!entry.writeDeleteUpdateToXml(writer, ewsObject)) {
367            writer.writeStartElement(XmlNamespace.Types, ewsObject
368                .getDeleteFieldXmlElementName());
369            this.writeUriToXml(writer, entry.getKey());
370            writer.writeEndElement();
371          }
372        }
373    
374        return true;
375      }
376    
377      /**
378       * Writes deletion update to XML.
379       *
380       * @param writer    the writer
381       * @param ewsObject the ews object
382       * @return True if property generated serialization.
383       */
384      public boolean writeDeleteUpdateToXml(EwsServiceXmlWriter writer,
385          ServiceObject ewsObject) {
386        return false;
387      }
388    }