@jaricardodev/ews-javascript-api
Version:
EWS Managed api in JavaScript
394 lines (356 loc) • 18.1 kB
text/typescript
import { ArgumentOutOfRangeException } from '../Exceptions/ArgumentException';
import { ArrayHelper, Convert, StringHelper } from '../ExtensionMethods';
import { CreateAttachmentException } from '../Exceptions/CreateAttachmentException';
import { CreateAttachmentResponse } from '../Core/Responses/CreateAttachmentResponse';
import { DeleteAttachmentException } from '../Exceptions/DeleteAttachmentException';
import { DeleteAttachmentResponse } from '../Core/Responses/DeleteAttachmentResponse';
import { EwsLogging } from "../Core/EwsLogging";
import { EwsUtilities } from "../Core/EwsUtilities";
import { ExchangeVersion } from '../Enumerations/ExchangeVersion';
import { FileAttachment } from "./FileAttachment";
import { ICustomUpdateSerializer } from "../Interfaces/ICustomXmlUpdateSerializer";
import { IOwnedProperty } from "../Interfaces/IOwnedProperty";
import { ISelfValidate } from "../Interfaces/ISelfValidate";
import { Item } from "../Core/ServiceObjects/Items/Item";
import { ItemAttachment } from "./ItemAttachment";
import { ItemAttachmentOf } from "./ItemAttachmentOf";
import { ItemInfo } from "../Core/ServiceObjects/Items/ItemInfo";
import { ServiceObject } from "../Core/ServiceObjects/ServiceObject";
import { ServiceResponseCollection } from '../Core/Responses/ServiceResponseCollection';
import { ServiceResult } from '../Enumerations/ServiceResult';
import { ServiceValidationException } from '../Exceptions/ServiceValidationException';
import { Strings } from "../Strings";
import { TypeContainer } from "../TypeContainer";
import { XmlElementNames } from "../Core/XmlElementNames";
import { Attachment } from "./Attachment";
import { ComplexPropertyCollection } from "./ComplexPropertyCollection";
/**
* Represents an item's attachment collection.
*/
export class AttachmentCollection extends ComplexPropertyCollection<Attachment> implements IOwnedProperty {
___typeGenerics: string[] = ["ComplexProperty"];
/**
* The item owner that owns this attachment collection
*/
private owner: Item = null;
/**
* @interface:IOwnedProperty The owner of this attachment collection.
*/
get Owner(): ServiceObject { return this.owner; };
set Owner(value) {
EwsLogging.Assert(
value != null && (value instanceof TypeContainer.Item), // instanceof Item), //info: can not check instanceof to avoid circular dependency in js. TypeContainer is workaround
"AttachmentCollection.IOwnedProperty.set_Owner",
"value is not a descendant of ItemBase");
this.owner = <Item>value;
}
/**
* @internal Initializes a new instance of AttachmentCollection.
*/
constructor() {
super();
}
/**
* Adds a file attachment to the collection.
*
* @param {string} fileName The name of the file representing the content of the attachment.
* @return {FileAttachment} A FileAttachment instance.
*/
private AddFileAttachmentXXXXX(fileName: string): FileAttachment;
/**
* Adds a file attachment to the collection.
*
* @param {string} name The display name of the new attachment.
* @param {string} fileName The name of the file representing the content of the attachment.
* @return {FileAttachment} A FileAttachment instance.
*/
private AddFileAttachmentXXXXX(name: string, fileName: string): FileAttachment;
//AddFileAttachmentXXXXX(name: string, contentStream: TextStreamBase /*System.IO.Stream*/): FileAttachment;
//AddFileAttachmentXXXXX(name: string, content: number[] /* Byte[] */): FileAttachment;
/**
* Adds a file attachment to the collection. - isContent parameter is required to be true to be able to use bas64 content directly
*
* @param {string} name The display name of the new attachment.
* @param {string} fileContent base64 ontent of the file representing the content of the attachment.
* @param {boolean} isContent if true used as base64 content of file.
* @return {FileAttachment} A FileAttachment instance.
*/
private AddFileAttachmentXXXXX(name: string, fileContent: string, isContent: boolean): FileAttachment;
private AddFileAttachmentXXXXX(nameOrFileName: string, fileNameOrContent?: string, isContent: boolean = false): FileAttachment {
let argsLength = arguments.length;
if (argsLength == 1) {
let name = nameOrFileName.split('\\').pop().split('/').pop();
return this.AddFileAttachment(name, fileNameOrContent);
}
if (argsLength === 2) {
throw new Error("AttachmentCollection.ts - Can only use this method with base64 content");
let fileAttachment: FileAttachment = new FileAttachment(this.owner);
fileAttachment.Name = 'name';
fileAttachment.FileName = fileNameOrContent;
this.InternalAdd(fileAttachment);
return fileAttachment;
}
if (argsLength == 3) {
if (isContent === true) {
let fileAttachment: FileAttachment = new FileAttachment(this.owner);
fileAttachment.Name = 'name';
fileAttachment.Base64Content = fileNameOrContent;
this.InternalAdd(fileAttachment);
return fileAttachment;
}
else {
return this.AddFileAttachment(nameOrFileName, fileNameOrContent);
}
}
new Error("AttachmentCollection.ts - AddFileAttachment - incorrect count of parameters");
}
/**
* Adds a file attachment to the collection. - isContent parameter is required to be true to be able to use bas64 content directly
*
* @param {string} name The display name of the new attachment.
* @param {string} fileContent base64 ontent of the file representing the content of the attachment.
* @return {FileAttachment} A FileAttachment instance.
*/
AddFileAttachment(name: string, content: string): FileAttachment {
let fileAttachment: FileAttachment = new FileAttachment(this.owner);
fileAttachment.Name = name;
fileAttachment.Base64Content = content;
this.InternalAdd(fileAttachment);
return fileAttachment;
}
/**
* Adds an item attachment to the collection
*
* @type <TItem> The type of the item to attach.
*
* @param {any*} TItem Item type, not instance
* @param {string} TItemElementName XML Element Name of the Item class
* @return {ItemAttachmentOf<TItem>} An ItemAttachment instance.
*/
AddItemAttachment<TItem extends Item>(TItem: typeof Item, TItemElementName: string): ItemAttachmentOf<TItem> {
if (typeof TItem.Attachable === 'undefined' || TItem.Attachable === false) {
throw new Error(
StringHelper.Format(
"Items of type {0} are not supported as attachments.",
TItem["name"])); //InvalidOperationException
}
let itemAttachment: ItemAttachmentOf<TItem> = new ItemAttachmentOf<TItem>(this.owner); //ref: //info: ItemAttachment can not be generic when same name non generic version exhist. TypeScript limitation
itemAttachment.Item = <TItem>(new ItemInfo()).CreateItemFromItemClass(itemAttachment, TItemElementName, true); //todo: needs to implement Reflector metadata for Type to class creation map
this.InternalAdd(<any><any>itemAttachment);
return itemAttachment;
}
/**
* Removes all attachments from this collection.
*/
Clear(): void { this.InternalClear(); }
/**
* @internal Disables the change log clearing mechanism. Attachment collections are saved separately from the items they belong to.
*/
ClearChangeLog(): void { /** Do nothing */ }
/**
* @internal Instantiate the appropriate attachment type depending on the current XML element name.
*
* @param {string} xmlElementName The XML element name from which to determine the type of attachment to create.
* @return {Attachment} An Attachment instance.
*/
CreateComplexProperty(xmlElementName: string): Attachment {
switch (xmlElementName) {
case XmlElementNames.FileAttachment:
return new FileAttachment(this.owner);
case XmlElementNames.ItemAttachment:
return <any><any>new ItemAttachment(this.owner);
default:
return null;
}
}
//JsonDeserializationNotImplementedException
CreateDefaultComplexProperty(): Attachment { EwsLogging.DebugLog("AttachmentCollection.ts - CreateDefaultComplexProperty : Not implemented."); return null; }
/**
* @internal Determines the name of the XML element associated with the complexProperty parameter.
*
* @param {Attachment} complexProperty The attachment object for which to determine the XML element name with.
* @return {string} The XML element name associated with the complexProperty parameter.
*/
GetCollectionItemXmlElementName(complexProperty: Attachment): string {
if (complexProperty instanceof FileAttachment) {
return XmlElementNames.FileAttachment;
}
else {
return XmlElementNames.ItemAttachment;
}
}
/**
* @internal Determines whether there are any unsaved attachment collection changes.
*
* @return {boolean} True if attachment adds or deletes haven't been processed yet.
*/
HasUnprocessedChanges(): boolean {
for (let attachment of this.Items) {
if (attachment.IsNew) {
return true;
}
}
// Any pending deletions?
for (let attachment of this.RemovedItems) {
if (!attachment.IsNew) {
return true;
}
}
// Recurse: process item attachments to check for new or deleted sub-attachments.
for (let itemAttachment of ArrayHelper.OfType<Attachment, ItemAttachment>(this.Items, (attach) => { return attach instanceof ItemAttachment; })) {
if (itemAttachment.Item != null) {
if (itemAttachment.Item.Attachments.HasUnprocessedChanges()) {
return true;
}
}
}
return false;
}
/**
* Calls the CreateAttachment web method to create a list of attachments.
*
* @param {string} parentItemId The Id of the parent item of the new attachments.
* @param {Attachment[]} attachments The attachments to create.
*/
private InternalCreateAttachments(parentItemId: string, attachments: Attachment[]): Promise<void> {
return this.owner.Service.CreateAttachments(parentItemId, attachments)
.then((responses: ServiceResponseCollection<CreateAttachmentResponse>) => {
for (let response of responses.Responses) {
// We remove all attachments that were successfully deleted from the change log. We should never
// receive a warning from EWS, so we ignore them.
if (response.Result != ServiceResult.Error) {
this.RemoveFromChangeLog(response.Attachment);
}
}
// TODO : Should we throw for warnings as well?
if (responses.OverallResult == ServiceResult.Error) {
throw new CreateAttachmentException(responses, Strings.AttachmentCreationFailed);
}
});
}
/**
* Calls the DeleteAttachment web method to delete a list of attachments.
*
* @param {Attachment[]} attachments The attachments to delete.
*/
private InternalDeleteAttachments(attachments: Attachment[]): Promise<void> {
return this.owner.Service.DeleteAttachments(attachments)
.then((responses: ServiceResponseCollection<DeleteAttachmentResponse>) => {
for (let response of responses.Responses) {
// We remove all attachments that were successfully deleted from the change log. We should never
// receive a warning from EWS, so we ignore them.
if (response.Result != ServiceResult.Error) {
this.RemoveFromChangeLog(response.Attachment);
}
}
// TODO : Should we throw for warnings as well?
if (responses.OverallResult == ServiceResult.Error) {
throw new DeleteAttachmentException(responses, Strings.AtLeastOneAttachmentCouldNotBeDeleted);
}
});
}
/**
* Removes the specified attachment.
*
* @param {Attachment} attachment The attachment to remove.
* @return {boolean} True if the attachment was successfully removed from the collection, false otherwise.
*/
Remove(attachment: Attachment): boolean {
EwsUtilities.ValidateParam(attachment, "attachment");
return this.InternalRemove(attachment);
}
/**
* Removes the attachment at the specified index.
*
* @param {number} index Index of the attachment to remove.
*/
RemoveAt(index: number): void {
if (index < 0 || index >= this.Count) {
throw new ArgumentOutOfRangeException("index", Strings.IndexIsOutOfRange);
}
this.InternalRemoveAt(index);
}
/**
* @internal Saves this collection by creating new attachment and deleting removed ones.
*/
Save(): Promise<void> {
let attachments = [];
// Retrieve a list of attachments that have to be deleted.
for (let attachment of this.RemovedItems) {
if (!attachment.IsNew) {
attachments.push(attachment);
}
}
// If any, delete them by calling the DeleteAttachment web method.
//promise resolves with null to keep chaining
return Promise.resolve(attachments.length > 0 ? this.InternalDeleteAttachments(attachments) : void 0)
.then<void>(() => {
attachments.splice(0);
})
.then(() => {
// Retrieve a list of attachments that have to be created.
for (let attachment of this.Items) {
if (attachment.IsNew) {
attachments.push(attachment);
}
}
// If there are any, create them by calling the CreateAttachment web method.
if (attachments.length > 0) {
let parentId = this.owner.IsAttachment ? this.owner.ParentAttachment.Id : this.owner.Id.UniqueId;
return this.InternalCreateAttachments(parentId, attachments);
} else {
return Promise.resolve();
}
}).then(() => {
// Process all of the item attachments in this collection.
let itemAttachments = ArrayHelper.OfType<Attachment, ItemAttachment>(attachments, (attachment) => attachment instanceof ItemAttachment);
return itemAttachments.reduce((prev, curr, index) => {
return prev.then<void>(() => {
return curr.Item.Attachments.Save().then(() => {
curr.Item.ClearChangeLog();
});
});
}, Promise.resolve());
}).then(() => {
super.ClearChangeLog();
});
}
/**
* @internal Validates this instance.
*/
Validate(): void {
// Validate all added attachments
let contactPhotoFound: boolean = false;
for (let attachmentIndex = 0; attachmentIndex < this.AddedItems.length; attachmentIndex++) {
let attachment: Attachment = this.AddedItems[attachmentIndex];
if (attachment.IsNew) {
// At the server side, only the last attachment with IsContactPhoto is kept, all other IsContactPhoto
// attachments are removed. CreateAttachment will generate AttachmentId for each of such attachments (although
// only the last one is valid).
//
// With E14 SP2 CreateItemWithAttachment, such request will only return 1 AttachmentId; but the client
// expects to see all, so let us prevent such "invalid" request in the first place.
//
// The IsNew check is to still let CreateAttachmentRequest allow multiple IsContactPhoto attachments.
//
if (this.owner.IsNew && this.owner.Service.RequestedServerVersion >= ExchangeVersion.Exchange2010_SP2) {
let fileAttachment: FileAttachment = <FileAttachment>attachment;
if (fileAttachment instanceof Attachment && fileAttachment.IsContactPhoto) {
if (contactPhotoFound) {
throw new ServiceValidationException(Strings.MultipleContactPhotosInAttachment);
}
contactPhotoFound = true;
}
}
attachment.Validate(attachmentIndex);
}
}
}
/**
* @internal Validates and saves this instance. **Not in official EWS source, to workaround some promise errors with validate and save**
*/
ValidateAndSave(): Promise<void> {
this.Validate();
return this.Save();
}
}