diff --git a/config/alfresco/messages/custommodel-restapi-messages.properties b/config/alfresco/messages/custommodel-restapi-messages.properties new file mode 100644 index 0000000000..d66fdf8fab --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=Model name can't be null. +cmm.rest_api.model_invalid=Invalid custom model. +cmm.rest_api.model_status_null=Model status can't be null. +cmm.rest_api.model_name_cannot_update=You can't update the model name. +cmm.rest_api.model_update_failure=We couldn't update the model. +cmm.rest_api.model_download_failure=We couldn't create a download node. +cmm.rest_api.model_namespace_uri_null=The model namespace URI can't be null. +cmm.rest_api.model_namespace_uri_invalid=The namespace URI is invalid. Use letters, numbers and URI characters only. +cmm.rest_api.model_namespace_prefix_null=Model namespace prefix can't be null. +# Type +cmm.rest_api.type_name_null=Type name can't be null. +cmm.rest_api.type_create_failure=We couldn't create the type. +cmm.rest_api.type_update_failure=We couldn't update the type. +cmm.rest_api.type_parent_cannot_update=You can't update the type's parent in an active custom model. +cmm.rest_api.type_parent_not_exist=You can''t set the type''s parent to ''{0}'', because the type ''{0}'' doesn''t exist. +cmm.rest_api.type_cannot_delete=You can't delete a type in an active model. +cmm.rest_api.type_delete_failure=We couldn't delete the type. +# Aspect +cmm.rest_api.aspect_name_null=Aspect name can't be null. +cmm.rest_api.aspect_create_failure=We couldn't create the aspect. +cmm.rest_api.aspect_update_failure=We couldn't update the aspect. +cmm.rest_api.aspect_parent_cannot_update=You can't update the aspect's parent in an active custom model. +cmm.rest_api.aspect_parent_not_exist=You can''t set the aspect''s parent to ''{0}'', because the aspect ''{0}'' doesn''t exist. +cmm.rest_api.aspect_cannot_delete=You can't delete an aspect in an active model. +cmm.rest_api.aspect_delete_failure=We couldn't delete the aspect. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=You can''t delete ''{0}'' as ''{1}'' depends on it. +# Constraint +cmm.rest_api.constraint_name_null=Constraint name can't be null. +cmm.rest_api.constraint_create_failure=We couldn't create constraint. +cmm.rest_api.constraint_parameter_name_null=Constraint parameter name can't be null. +cmm.rest_api.constraint_type_null=Custom constraint must have a 'type' attribute. +cmm.rest_api.constraint_ref_not_defined=Constraint reference ''{0}'' isn'' t defined by this model. +cmm.rest_api.regex_constraint_invalid_expression=REGEX expression ''{0}'' isn''t valid. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' isn''t a valid ''double'' value for MINMAX parameter ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=Maximum value of the MINMAX constraint must be greater than zero. +cmm.rest_api.minmax_constraint_invalid_use=MINMAX constraint can only be used with numeric data type. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' isn''t a valid ''int'' value for LENGTH parameter ''{1}''. +cmm.rest_api.length_constraint_invalid_use=You can only use the LENGTH constraint with TEXT, CONTENT and MLTEXT data types. +# Property +cmm.rest_api.property_name_null=Property name can't be null. +cmm.rest_api.property_create_update_failure=We couldn't create/update property. +cmm.rest_api.property_delete_failure=We couldn't delete the property. +cmm.rest_api.property_datatype_invalid=''{0}'' isn''t a valid data type format. A valid format should consist of a namespace prefix, a colon, and a name, for example: d:text. +cmm.rest_api.properties_empty_null=Properties can't be empty or null. +cmm.rest_api.property_create_name_already_in_use=We couldn''t create the property as the property name ''{0}'' is already in use. +cmm.rest_api.property_update_prop_not_found=We couldn''t find a property that matches ''{0}''. +cmm.rest_api.property_change_datatype_err=You can't change the data type of a property in an active model. +cmm.rest_api.property_change_mandatory_opt_err=You can't change the mandatory option of a property in an active model. +cmm.rest_api.property_change_mandatory_enforced_opt_err=You can't change the mandatory-enforced option of a property in an active model. +cmm.rest_api.property_change_multi_valued_opt_err=You can't change the multi-valued option of a property in an active model. +# validation +cmm.rest_api.input_validation_err=''{0}'' isn''t a valid value. Use numbers, letters, hyphens (-) and underscores (_) only. +cmm.rest_api.prefix_not_registered=There isn''t a namespace prefix registered for the URI ''{0}''. Make sure the model is active. +cmm.rest_api.prefixed_qname_invalid=''{0}'' isn''t a valid prefixed QName value. {1} +cmm.rest_api.circular_dependency_err=A circular dependency was detected. You can''t set parent ''{0}'', as it''s model already depends on ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' isn''t a valid prefixed QName format. A valid format should consist of a namespace prefix, a colon, and a name, for example: cm:content. +# model import +cmm.rest_api.model.import_namespace_multiple_found=The custom model can only have one namespace. We found ''{0}''. +cmm.rest_api.model.import_namespace_undefined=The custom model must define a namespace. +cmm.rest_api.model.import_associations_unsupported=The custom model doesn't support the 'associations' element. +cmm.rest_api.model.import_overrides_unsupported=The custom model doesn't support the 'overrides' element. +cmm.rest_api.model.import_mandatory_aspects_unsupported=The custom model doesn't support the 'mandatory-aspects' element. +cmm.rest_api.model.import_archive_unsupported=The custom model doesn't support the 'archive' element. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=The custom model doesn't support the 'includedInSuperTypeQuery' element. +cmm.rest_api.model.import_not_multi_part_req=Request isn't a multi-part form data. +cmm.rest_api.model.import_not_zip_format=''{0}'' isn''t a valid zip file. +cmm.rest_api.model.import_process_zip_file_failure=We couldn't process the zip file. +cmm.rest_api.model.import_no_zip_file_uploaded=We couldn't upload the zip file. +cmm.rest_api.model.import_invalid_zip_package=The zip file can't have more than two files. There should be one Model file and one Extension module file. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' isn''t a model or a Share extension module file. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' isn''t a valid model file. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' isn''t a valid Share extension module file. +cmm.rest_api.model.import_failure=We couldn't import the model. +cmm.rest_api.model.import_process_ext_module_file_failure=We couldn't process the Share extension module file. diff --git a/config/alfresco/messages/custommodel-restapi-messages_de.properties b/config/alfresco/messages/custommodel-restapi-messages_de.properties new file mode 100644 index 0000000000..5d9bb5cf76 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_de.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=Modellname darf nicht Null sein. +cmm.rest_api.model_invalid=Ung\u00fcltiges parametriertes Modell. +cmm.rest_api.model_status_null=Modellstatus darf nicht Null sein. +cmm.rest_api.model_name_cannot_update=Sie k\u00f6nnen den Modellnamen nicht aktualisieren. +cmm.rest_api.model_update_failure=Wir konnten das Modell nicht aktualisieren. +cmm.rest_api.model_download_failure=Wir konnten keinen Download-Knoten erstellen. +cmm.rest_api.model_namespace_uri_null=Das Modell-Namespace-URI darf nicht Null sein. +cmm.rest_api.model_namespace_uri_invalid=Das Namespace-URI ist ung\u00fcltig. Verwenden Sie nur Buchstaben, Zahlen und URI-Zeichen. +cmm.rest_api.model_namespace_prefix_null=Modell-Namespace-Pr\u00e4fix darf nicht Null sein. +# Type +cmm.rest_api.type_name_null=Typenname darf nicht Null sein. +cmm.rest_api.type_create_failure=Wir konnten den Typ nicht erstellen. +cmm.rest_api.type_update_failure=Wir konnten den Typ nicht aktualisieren. +cmm.rest_api.type_parent_cannot_update=Sie k\u00f6nnen den Elterntyp in einem aktiven parametrierten Modell nicht aktualisieren. +cmm.rest_api.type_parent_not_exist=Sie k\u00f6nnen den Elterntyp nicht auf ''{0}'' setzen, weil es den Typ ''{0}'' nicht gibt. +cmm.rest_api.type_cannot_delete=Sie k\u00f6nnen einen Typ in einem aktiven Modell nicht l\u00f6schen. +cmm.rest_api.type_delete_failure=Wir konnten den Typ nicht l\u00f6schen. +# Aspect +cmm.rest_api.aspect_name_null=Aspektname darf nicht Null sein. +cmm.rest_api.aspect_create_failure=Wir konnten den Aspekt nicht erstellen. +cmm.rest_api.aspect_update_failure=Wir konnten den Aspekt nicht aktualisieren. +cmm.rest_api.aspect_parent_cannot_update=Sie k\u00f6nnen den Elternaspekt in einem aktiven parametrierten Modell nicht aktualisieren. +cmm.rest_api.aspect_parent_not_exist=Sie k\u00f6nnen den Elternaspekt nicht auf ''{0}'' setzen, weil es den Aspekt ''{0}'' nicht gibt. +cmm.rest_api.aspect_cannot_delete=Sie k\u00f6nnen einen Aspekt in einem aktiven Modell nicht l\u00f6schen. +cmm.rest_api.aspect_delete_failure=Wir konnten den Aspekt nicht l\u00f6schen. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=Sie k\u00f6nnen ''{0}'' nicht l\u00f6schen, weil ''{1}'' davon abh\u00e4ngt. +# Constraint +cmm.rest_api.constraint_name_null=Constraint-Name darf nicht Null sein. +cmm.rest_api.constraint_create_failure=Wir konnten den Constraint nicht erstellen. +cmm.rest_api.constraint_parameter_name_null=Constraint-Parametername darf nicht Null sein. +cmm.rest_api.constraint_type_null=Parametrierter Constraint muss ein Typenattribut haben. +cmm.rest_api.constraint_ref_not_defined=Constraint-Verweis ''{0}'' ist von diesem Modell nicht definiert. +cmm.rest_api.regex_constraint_invalid_expression=REGEX-Ausdruck ''{0}'' ist nicht zul\u00e4ssig. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' ist kein zul\u00e4ssiger Doppelparameter f\u00fcr MINMAX-Parameter ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=H\u00f6chstwert des MINMAX-Constraints muss gr\u00f6\u00dfer als Null sein. +cmm.rest_api.minmax_constraint_invalid_use=MINMAX-Constraint kann nur mit numerischem Datentyp verwendet werden. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' ist kein zul\u00e4ssiger Integer-Wert f\u00fcr LENGTH-Parameter ''{1}''. +cmm.rest_api.length_constraint_invalid_use=Sie k\u00f6nnen den LENGTH-Constraint nur mit den Datentypen TEXT, CONTENT und MLTEXT verwenden. +# Property +cmm.rest_api.property_name_null=Eigenschaftenname darf nicht Null sein. +cmm.rest_api.property_create_update_failure=Wir konnten die Eigenschaft nicht erstellen/aktualisieren. +cmm.rest_api.property_delete_failure=Wir konnten die Eigenschaft nicht l\u00f6schen. +cmm.rest_api.property_datatype_invalid=''{0}'' ist kein zul\u00e4ssiges Datentyp-Format. Ein zul\u00e4ssiges Format besteht aus einem Namespace-Pr\u00e4fix, einem Doppelpunkt und einem Namen (zum Beispiel d:text). +cmm.rest_api.properties_empty_null=Eigenschaften d\u00fcrfen nicht leer oder Null sein. +cmm.rest_api.property_create_name_already_in_use=Wir konnten die Eigenschaft nicht erstellen, weil der Eigenschaftenname ''{0}'' bereits verwendet wird. +cmm.rest_api.property_update_prop_not_found=Wir konnten keine Eigenschaft finden, die mit ''{0}'' \u00fcbereinstimmt. +cmm.rest_api.property_change_datatype_err=Der Datentyp einer Eigenschaft in einem aktiven Modell kann nicht ge\u00e4ndert werden. +cmm.rest_api.property_change_mandatory_opt_err=Die Option ''Obligatorisch'' einer Eigenschaft in einem aktiven Modell kann nicht ge\u00e4ndert werden. +cmm.rest_api.property_change_mandatory_enforced_opt_err=Die Option ''Obligatorisch (erzwungen)'' einer Eigenschaft in einem aktiven Modell kann nicht ge\u00e4ndert werden. +cmm.rest_api.property_change_multi_valued_opt_err=Die Option f\u00fcr mehrere Werte einer Eigenschaft in einem aktiven Modell kann nicht ge\u00e4ndert werden. +# validation +cmm.rest_api.input_validation_err=''{0}'' ist kein zul\u00e4ssiger Wert. Verwenden Sie nur Zahlen, Buchstaben, Bindestriche (-) und Unterstriche (_). +cmm.rest_api.prefix_not_registered=F\u00fcr das URI ''{0}'' ist kein Namespace-Pr\u00e4fix registriert. Stellen Sie sicher, dass das Modell aktiv ist. +cmm.rest_api.prefixed_qname_invalid=''{0}'' ist kein zul\u00e4ssiger QName-Wert mit Pr\u00e4fix. {1} +cmm.rest_api.circular_dependency_err=Es wurde eine Ringabh\u00e4ngigkeit entdeckt. Eltern-''{0}'' kann nicht eingerichtet werden, weil sein Modell bereits von ''{1}'' abh\u00e4ngt. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' ist kein zul\u00e4ssiges QName-Format mit Pr\u00e4fix. Ein zul\u00e4ssiges Format besteht aus einem Namespace-Pr\u00e4fix, einem Doppelpunkt und einem Namen (zum Beispiel cm:content). +# model import +cmm.rest_api.model.import_namespace_multiple_found=Das parametrierte Modell darf nur \u00fcber einen Namespace verf\u00fcgen. Wir haben ''{0}'' gefunden. +cmm.rest_api.model.import_namespace_undefined=Das parametrierte Modell muss einen Namespace definieren. +cmm.rest_api.model.import_associations_unsupported=Das parametrierte Modell unterst\u00fctzt das Element 'associations' nicht. +cmm.rest_api.model.import_overrides_unsupported=Das parametrierte Modell unterst\u00fctzt das Element 'overrides' nicht. +cmm.rest_api.model.import_mandatory_aspects_unsupported=Das parametrierte Modell unterst\u00fctzt das Element 'mandatory-aspects' nicht. +cmm.rest_api.model.import_archive_unsupported=Das parametrierte Modell unterst\u00fctzt das Element 'archive' nicht. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=Das parametrierte Modell unterst\u00fctzt das Element 'includedInSuperTypeQuery' nicht. +cmm.rest_api.model.import_not_multi_part_req=Bei der Anfrage handelt es sich nicht um mehrteilige Formulardaten. +cmm.rest_api.model.import_not_zip_format=''{0}'' ist keine zul\u00e4ssige ZIP-Datei. +cmm.rest_api.model.import_process_zip_file_failure=Wir konnten die ZIP-Datei nicht verarbeiten. +cmm.rest_api.model.import_no_zip_file_uploaded=Wir konnten die ZIP-Datei nicht hochladen. +cmm.rest_api.model.import_invalid_zip_package=Die ZIP-Datei darf nicht mehr als zwei Dateien beinhalten. Es sollte eine Modelldatei und eine Erweiterungsmodul-Datei geben. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' ist keine Modell- oder Share-Erweiterungs-Modul-Datei. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' ist keine zul\u00e4ssige Modelldatei. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' ist keine zul\u00e4ssige Share-Erweiterungs-Modul-Datei. +cmm.rest_api.model.import_failure=Wir konnten das Modell nicht importieren. +cmm.rest_api.model.import_process_ext_module_file_failure=Wir konnten die Share-Erweiterungs-Modul-Datei nicht verarbeiten. diff --git a/config/alfresco/messages/custommodel-restapi-messages_es.properties b/config/alfresco/messages/custommodel-restapi-messages_es.properties new file mode 100644 index 0000000000..1a695575d5 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_es.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=El nombre del modelo no puede ser nulo. +cmm.rest_api.model_invalid=Modelo personalizado no v\u00e1lido. +cmm.rest_api.model_status_null=El estado del modelo no puede ser nulo. +cmm.rest_api.model_name_cannot_update=No puede actualizar el nombre del modelo. +cmm.rest_api.model_update_failure=No se ha podido actualizar el modelo. +cmm.rest_api.model_download_failure=No se ha podido crear un nodo de descarga. +cmm.rest_api.model_namespace_uri_null=La URI de espacio de nombres del modelo no puede ser nula. +cmm.rest_api.model_namespace_uri_invalid=La URI de espacio de nombres no es v\u00e1lida. Utilice n\u00fameros, letras y caracteres URI solamente. +cmm.rest_api.model_namespace_prefix_null=El prefijo de espacio de nombres no puede ser nulo. +# Type +cmm.rest_api.type_name_null=El nombre del tipo no puede ser nulo. +cmm.rest_api.type_create_failure=No se ha podido crear el tipo. +cmm.rest_api.type_update_failure=No se ha podido actualizar el tipo. +cmm.rest_api.type_parent_cannot_update=No puede actualizar el padre del tipo en un modelo personalizado activo. +cmm.rest_api.type_parent_not_exist=No puede establecer el padre del tipo en ''{0}'', porque el tipo ''{0}'' no existe. +cmm.rest_api.type_cannot_delete=No puede eliminar un tipo en un modelo activo. +cmm.rest_api.type_delete_failure=No se ha podido eliminar el tipo. +# Aspect +cmm.rest_api.aspect_name_null=El nombre del aspecto no puede ser nulo. +cmm.rest_api.aspect_create_failure=No se ha podido crear el aspecto. +cmm.rest_api.aspect_update_failure=No se ha podido actualizar el aspecto. +cmm.rest_api.aspect_parent_cannot_update=No puede actualizar el padre del aspecto en un modelo personalizado activo. +cmm.rest_api.aspect_parent_not_exist=No puede establecer el padre del aspecto en ''{0}'', porque el aspecto ''{0}'' no existe. +cmm.rest_api.aspect_cannot_delete=No puede eliminar un aspecto en un modelo activo. +cmm.rest_api.aspect_delete_failure=No se ha podido eliminar el aspecto. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=No puede eliminar ''{0}'' ya que ''{1}'' depende de \u00e9l. +# Constraint +cmm.rest_api.constraint_name_null=El nombre de la restricci\u00f3n no puede ser nulo. +cmm.rest_api.constraint_create_failure=No se ha podido crear la restricci\u00f3n. +cmm.rest_api.constraint_parameter_name_null=El nombre del par\u00e1metro de la restricci\u00f3n no puede ser nulo. +cmm.rest_api.constraint_type_null=La restricci\u00f3n personalizada debe tener un atributo 'tipo'. +cmm.rest_api.constraint_ref_not_defined=La referencia de la restricci\u00f3n ''{0}'' no la ha definido este modelo. +cmm.rest_api.regex_constraint_invalid_expression=La expresi\u00f3n REGEX ''{0}'' no es v\u00e1lida. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' no es un valor doble v\u00e1lido para el par\u00e1metro MINMAX ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=El valor m\u00e1ximo de la restricci\u00f3n MINMAX debe ser superior a cero. +cmm.rest_api.minmax_constraint_invalid_use=La restricci\u00f3n MINMAX solo puede usarse con un tipo de datos num\u00e9ricos. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' no es un valor ''entero'' v\u00e1lido para el par\u00e1metro LENGTH ''{1}''. +cmm.rest_api.length_constraint_invalid_use=Solo puede usar la restricci\u00f3n LENGTH con los tipos de datos TEXT, CONTENT y MLTEXT. +# Property +cmm.rest_api.property_name_null=El nombre de la propiedad no puede ser nulo. +cmm.rest_api.property_create_update_failure=No se ha podido crear/actualizar la propiedad. +cmm.rest_api.property_delete_failure=No se ha podido eliminar la propiedad. +cmm.rest_api.property_datatype_invalid=''{0}'' no es un formato de tipo de datos v\u00e1lido. Un formato v\u00e1lido debe incluir un prefijo de espacio de nombres, dos puntos y un nombre, por ejemplo, d:text. +cmm.rest_api.properties_empty_null=Las propiedades no pueden estar vac\u00edas o ser nulas. +cmm.rest_api.property_create_name_already_in_use=No se ha podido crear la propiedad porque el nombre de propiedad ''{0}'' ya se est\u00e1 usando. +cmm.rest_api.property_update_prop_not_found=No se ha podido encontrar una propiedad que coincida con ''{0}''. +cmm.rest_api.property_change_datatype_err=No puede cambiar el tipo de datos de una propiedad en un modelo activo. +cmm.rest_api.property_change_mandatory_opt_err=No puede cambiar la opci\u00f3n obligatoria de una propiedad en un modelo activo. +cmm.rest_api.property_change_mandatory_enforced_opt_err=No puede cambiar la opci\u00f3n obligatoria (exigida) de una propiedad en un modelo activo. +cmm.rest_api.property_change_multi_valued_opt_err=No puede cambiar la opci\u00f3n de m\u00faltiples valores de una propiedad en un modelo activo. +# validation +cmm.rest_api.input_validation_err=''{0}'' no es un valor v\u00e1lido. Utilice n\u00fameros, letras, guiones (-) y guiones bajos (_) solamente. +cmm.rest_api.prefix_not_registered=No existe un prefijo de espacio de nombres registrado para la URI ''{0}''. Aseg\u00farese de que el modelo est\u00e1 activo. +cmm.rest_api.prefixed_qname_invalid=''{0}'' no es un valor QName con prefijo v\u00e1lido. {1} +cmm.rest_api.circular_dependency_err=Se ha detectado una dependencia circular. No puede establecer el padre ''{0}'', ya que su modelo ya depende de ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' no es un formato QName con prefijo v\u00e1lido. Un formato v\u00e1lido debe incluir un prefijo de espacio de nombres, dos puntos y un nombre, por ejemplo, cm:content. +# model import +cmm.rest_api.model.import_namespace_multiple_found=El modelo personalizado solo puede tener un espacio de nombres. Se encontr\u00f3 ''{0}''. +cmm.rest_api.model.import_namespace_undefined=El modelo personalizado debe definir un espacio de nombres. +cmm.rest_api.model.import_associations_unsupported=El modelo personalizado no es compatible con el elemento 'associations'. +cmm.rest_api.model.import_overrides_unsupported=El modelo personalizado no es compatible con el elemento 'overrides'. +cmm.rest_api.model.import_mandatory_aspects_unsupported=El modelo personalizado no es compatible con el elemento 'mandatory-aspects'. +cmm.rest_api.model.import_archive_unsupported=El modelo personalizado no es compatible con el elemento 'archive'. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=El modelo personalizado no es compatible con el elemento 'includedInSuperTypeQuery'. +cmm.rest_api.model.import_not_multi_part_req=La solicitud no equivale a datos de formulario de varias partes. +cmm.rest_api.model.import_not_zip_format=''{0}'' no es un fichero zip v\u00e1lido. +cmm.rest_api.model.import_process_zip_file_failure=No se ha podido procesar el fichero zip. +cmm.rest_api.model.import_no_zip_file_uploaded=No se ha podido subir el fichero zip. +cmm.rest_api.model.import_invalid_zip_package=El fichero zip no puede tener m\u00e1s de dos ficheros. Debe hacer un fichero de modelo y un fichero de m\u00f3dulo de extensi\u00f3n. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' no es un modelo ni un fichero de m\u00f3dulo de extensi\u00f3n de Share. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' no es un fichero de modelo v\u00e1lido. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' no es un fichero de m\u00f3dulo de extensi\u00f3n de Share v\u00e1lido. +cmm.rest_api.model.import_failure=No se ha podido importar el modelo. +cmm.rest_api.model.import_process_ext_module_file_failure=No se ha podido procesar el fichero de m\u00f3dulo de extensi\u00f3n de Share. diff --git a/config/alfresco/messages/custommodel-restapi-messages_fr.properties b/config/alfresco/messages/custommodel-restapi-messages_fr.properties new file mode 100644 index 0000000000..92890ec665 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_fr.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=Le nom du mod\u00e8le ne peut pas \u00eatre null. +cmm.rest_api.model_invalid=Mod\u00e8le personnalis\u00e9 non valide. +cmm.rest_api.model_status_null=Le statut du mod\u00e8le ne peut pas \u00eatre null. +cmm.rest_api.model_name_cannot_update=Impossible de mettre \u00e0 jour le nom du mod\u00e8le. +cmm.rest_api.model_update_failure=Impossible de mettre \u00e0 jour le mod\u00e8le. +cmm.rest_api.model_download_failure=Impossible de cr\u00e9er un n\u0153ud de t\u00e9l\u00e9chargement. +cmm.rest_api.model_namespace_uri_null=L'URI d'espace de nom du mod\u00e8le ne peut pas \u00eatre null. +cmm.rest_api.model_namespace_uri_invalid=L'URI d'espace de nom n'est pas valide. Utilisez uniquement des lettres, des chiffres et des caract\u00e8res URI. +cmm.rest_api.model_namespace_prefix_null=Le pr\u00e9fixe d'espace de nom du mod\u00e8le ne peut pas \u00eatre null. +# Type +cmm.rest_api.type_name_null=Le nom du type ne peut pas \u00eatre null. +cmm.rest_api.type_create_failure=Impossible de cr\u00e9er le type. +cmm.rest_api.type_update_failure=Impossible de mettre \u00e0 jour le type. +cmm.rest_api.type_parent_cannot_update=Impossible de mettre \u00e0 jour le parent du type dans un mod\u00e8le personnalis\u00e9 actif. +cmm.rest_api.type_parent_not_exist=Impossible de d\u00e9finir le parent du type sur ''{0}'', car le type ''{0}'' n''existe pas. +cmm.rest_api.type_cannot_delete=Impossible de supprimer un type dans un mod\u00e8le actif. +cmm.rest_api.type_delete_failure=Impossible de supprimer le type. +# Aspect +cmm.rest_api.aspect_name_null=Le nom de l'aspect ne peut pas \u00eatre null. +cmm.rest_api.aspect_create_failure=Impossible de cr\u00e9er l'aspect. +cmm.rest_api.aspect_update_failure=Impossible de mettre \u00e0 jour l'aspect. +cmm.rest_api.aspect_parent_cannot_update=Impossible de mettre \u00e0 jour le parent de l'aspect dans un mod\u00e8le personnalis\u00e9 actif. +cmm.rest_api.aspect_parent_not_exist=Impossible de d\u00e9finir le parent de l''aspect sur ''{0}'', car l''aspect ''{0}'' n''existe pas. +cmm.rest_api.aspect_cannot_delete=Impossible de supprimer un aspect dans un mod\u00e8le actif. +cmm.rest_api.aspect_delete_failure=Impossible de supprimer l'aspect. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=Impossible de supprimer ''{0}'' car ''{1}'' en d\u00e9pend. +# Constraint +cmm.rest_api.constraint_name_null=Le nom de la contrainte ne peut pas \u00eatre null. +cmm.rest_api.constraint_create_failure=Impossible de cr\u00e9er la contrainte. +cmm.rest_api.constraint_parameter_name_null=Le nom du param\u00e8tre de la contrainte ne peut pas \u00eatre null. +cmm.rest_api.constraint_type_null=La contrainte personnalis\u00e9e doit avoir un attribut 'type'. +cmm.rest_api.constraint_ref_not_defined=La r\u00e9f\u00e9rence de la contrainte ''{0}'' n''est pas d\u00e9finie par ce mod\u00e8le. +cmm.rest_api.regex_constraint_invalid_expression=L''expression REGEX ''{0}'' n''est pas valide. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' n''est pas une valeur ''double'' valide pour le param\u00e8tre MINMAX ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=La valeur maximum de la contrainte MINMAX doit \u00eatre sup\u00e9rieure \u00e0 z\u00e9ro. +cmm.rest_api.minmax_constraint_invalid_use=La contrainte MINMAX peut \u00eatre utilis\u00e9e uniquement avec un type de donn\u00e9es num\u00e9rique. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' n''est pas une valeur ''entier'' valide pour le param\u00e8tre LENGTH ''{1}''. +cmm.rest_api.length_constraint_invalid_use=La contrainte LENGTH peut \u00eatre utilis\u00e9e uniquement avec les types de donn\u00e9es TEXT, CONTENT et MLTEXT. +# Property +cmm.rest_api.property_name_null=Le nom de la propri\u00e9t\u00e9 ne peut pas \u00eatre null. +cmm.rest_api.property_create_update_failure=Impossible de cr\u00e9er/mettre \u00e0 jour la propri\u00e9t\u00e9. +cmm.rest_api.property_delete_failure=Impossible de supprimer la propri\u00e9t\u00e9. +cmm.rest_api.property_datatype_invalid=''{0}'' n''est pas un format de type de donn\u00e9es valide. Un format valide se compose d''un pr\u00e9fixe d''espace de nom, suivi de deux-points et d''un nom, par exemple\u00a0: d:text. +cmm.rest_api.properties_empty_null=Les propri\u00e9t\u00e9s ne peuvent pas \u00eatre vides ou null. +cmm.rest_api.property_create_name_already_in_use=Impossible de cr\u00e9er la propri\u00e9t\u00e9 car le nom de propri\u00e9t\u00e9 ''{0}'' est d\u00e9j\u00e0 utilis\u00e9. +cmm.rest_api.property_update_prop_not_found=Impossible de trouver une propri\u00e9t\u00e9 qui correspond \u00e0 ''{0}''. +cmm.rest_api.property_change_datatype_err=Vous ne pouvez pas modifier le type de donn\u00e9es d'une propri\u00e9t\u00e9 dans un mod\u00e8le actif. +cmm.rest_api.property_change_mandatory_opt_err=Vous ne pouvez pas modifier l'option obligatoire d'une propri\u00e9t\u00e9 dans un mod\u00e8le actif. +cmm.rest_api.property_change_mandatory_enforced_opt_err=Vous ne pouvez pas modifier l'option obligatoire impos\u00e9e d'une propri\u00e9t\u00e9 dans un mod\u00e8le actif. +cmm.rest_api.property_change_multi_valued_opt_err=Vous ne pouvez pas modifier l'option \u00e0 valeurs multiples d'une propri\u00e9t\u00e9 dans un mod\u00e8le actif. +# validation +cmm.rest_api.input_validation_err=''{0}'' n''est pas une valeur valide. Utilisez des chiffres, des lettres, des tirets (-) et des traits de soulignement (_) uniquement. +cmm.rest_api.prefix_not_registered=Aucun pr\u00e9fixe d''espace de nom enregistr\u00e9 pour l''URI ''{0}''. Assurez-vous que le mod\u00e8le est actif. +cmm.rest_api.prefixed_qname_invalid=''{0}'' n''est pas une valeur QName pr\u00e9fix\u00e9e valide. {1} +cmm.rest_api.circular_dependency_err=Une d\u00e9pendance circulaire a \u00e9t\u00e9 d\u00e9tect\u00e9e. Impossible de d\u00e9finir le parent ''{0}'' car son mod\u00e8le d\u00e9pend d\u00e9j\u00e0 de ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' n''est pas un format QName pr\u00e9fix\u00e9 valide. Un format valide se compose d''un pr\u00e9fixe d''espace de nom, suivi de deux-points et d''un nom, par exemple\u00a0: cm:content. +# model import +cmm.rest_api.model.import_namespace_multiple_found=Le mod\u00e8le personnalis\u00e9 ne peut avoir qu''un seul espace de nom. ''{0}'' a \u00e9t\u00e9 trouv\u00e9. +cmm.rest_api.model.import_namespace_undefined=Le mod\u00e8le personnalis\u00e9 doit d\u00e9finir un espace de nom. +cmm.rest_api.model.import_associations_unsupported=Le mod\u00e8le personnalis\u00e9 ne prend pas en charge l'\u00e9l\u00e9ment 'associations'. +cmm.rest_api.model.import_overrides_unsupported=Le mod\u00e8le personnalis\u00e9 ne prend pas en charge l'\u00e9l\u00e9ment 'overrides'. +cmm.rest_api.model.import_mandatory_aspects_unsupported=Le mod\u00e8le personnalis\u00e9 ne prend pas en charge l'\u00e9l\u00e9ment 'mandatory-aspects'. +cmm.rest_api.model.import_archive_unsupported=Le mod\u00e8le personnalis\u00e9 ne prend pas en charge l'\u00e9l\u00e9ment 'archive'. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=Le mod\u00e8le personnalis\u00e9 ne prend pas en charge l'\u00e9l\u00e9ment 'includedInSuperTypeQuery'. +cmm.rest_api.model.import_not_multi_part_req=La requ\u00eate n'est pas une donn\u00e9e de formulaire \u00e0 parties multiples. +cmm.rest_api.model.import_not_zip_format=''{0}'' n''est pas un fichier zip valide. +cmm.rest_api.model.import_process_zip_file_failure=Impossible de traiter le fichier zip. +cmm.rest_api.model.import_no_zip_file_uploaded=Impossible d'importer le fichier zip. +cmm.rest_api.model.import_invalid_zip_package=Le fichier zip ne peut pas comporter plus de deux fichiers. Il doit comporter un fichier de mod\u00e8le et un fichier de module d'extension. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' n''est pas un fichier de mod\u00e8le ou de module d''extension Share. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' n''est pas un fichier de mod\u00e8le valide. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' n''est pas un fichier de module d''extension Share valide. +cmm.rest_api.model.import_failure=Impossible d'importer le mod\u00e8le. +cmm.rest_api.model.import_process_ext_module_file_failure=Impossible de traiter le fichier de module d'extension Share. diff --git a/config/alfresco/messages/custommodel-restapi-messages_it.properties b/config/alfresco/messages/custommodel-restapi-messages_it.properties new file mode 100644 index 0000000000..10192f6180 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_it.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=Il nome modello non pu\u00f2 essere nullo. +cmm.rest_api.model_invalid=Modello personalizzato non valido. +cmm.rest_api.model_status_null=Lo stato del modello non pu\u00f2 essere nullo. +cmm.rest_api.model_name_cannot_update=Impossibile aggiornare il nome modello. +cmm.rest_api.model_update_failure=Impossibile aggiornare il modello. +cmm.rest_api.model_download_failure=Impossibile creare un nodo di download. +cmm.rest_api.model_namespace_uri_null=L'URI dello spazio dei nomi del modello non pu\u00f2 essere nullo. +cmm.rest_api.model_namespace_uri_invalid=L'URI dello spazio dei nomi non \u00e8 valido. Utilizzare solo lettere, numeri e caratteri URI. +cmm.rest_api.model_namespace_prefix_null=Il prefisso dello spazio dei nomi del modello non pu\u00f2 essere nullo. +# Type +cmm.rest_api.type_name_null=Il nome tipo non pu\u00f2 essere nullo. +cmm.rest_api.type_create_failure=Impossibile creare il tipo. +cmm.rest_api.type_update_failure=Impossibile aggiornare il tipo. +cmm.rest_api.type_parent_cannot_update=Impossibile aggiornare il genitore del tipo in un modello personalizzato attivo. +cmm.rest_api.type_parent_not_exist=Impossibile impostare il genitore del tipo su ''{0}'' perch\u00e9 il tipo ''{0}'' non esiste. +cmm.rest_api.type_cannot_delete=Impossibile eliminare un tipo in un modello attivo. +cmm.rest_api.type_delete_failure=Impossibile eliminare il tipo. +# Aspect +cmm.rest_api.aspect_name_null=Il nome aspetto non pu\u00f2 essere nullo. +cmm.rest_api.aspect_create_failure=Impossibile creare l'aspetto. +cmm.rest_api.aspect_update_failure=Impossibile aggiornare l'aspetto. +cmm.rest_api.aspect_parent_cannot_update=Impossibile aggiornare il genitore dell'aspetto in un modello personalizzato attivo. +cmm.rest_api.aspect_parent_not_exist=Impossibile impostare il genitore dell''aspetto su ''{0}'' perch\u00e9 l''aspetto ''{0}'' non esiste. +cmm.rest_api.aspect_cannot_delete=Impossibile eliminare un aspetto in un modello attivo. +cmm.rest_api.aspect_delete_failure=Impossibile eliminare l'aspetto. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=Impossibile eliminare ''{0}'' perch\u00e9 ''{1}'' dipende da questo. +# Constraint +cmm.rest_api.constraint_name_null=Il nome vincolo non pu\u00f2 essere nullo. +cmm.rest_api.constraint_create_failure=Impossibile creare il vincolo. +cmm.rest_api.constraint_parameter_name_null=Il parametro del vincolo non pu\u00f2 essere nullo. +cmm.rest_api.constraint_type_null=Il vincolo personalizzato deve avere un attributo ''tipo''. +cmm.rest_api.constraint_ref_not_defined=Il riferimento vincolo ''{0}'' non \u00e8 definito da questo modello. +cmm.rest_api.regex_constraint_invalid_expression=L''espressione REGEX ''{0}'' non \u00e8 valida. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' non \u00e8 un valore ''doppio'' valido per il parametro MINMAX ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=Il valore massimo del vincolo MINMAX deve essere maggiore di zero. +cmm.rest_api.minmax_constraint_invalid_use=Il vincolo MINMAX pu\u00f2 essere utilizzato solo con il tipo di dati numerico. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' non \u00e8 un valore ''intero'' valido per il parametro LENGTH ''{1}''. +cmm.rest_api.length_constraint_invalid_use=\u00c8 possibile utilizzare solo il vincolo LENGTH con i tipi di dati TEXT, CONTENT e MLTEXT. +# Property +cmm.rest_api.property_name_null=Il nome propriet\u00e0 non pu\u00f2 essere nullo. +cmm.rest_api.property_create_update_failure=Impossibile creare/aggiornare la propriet\u00e0. +cmm.rest_api.property_delete_failure=Impossibile eliminare la propriet\u00e0. +cmm.rest_api.property_datatype_invalid=''{0}'' non \u00e8 un formato tipo di dati valido. Un formato valido deve essere costituito da un prefisso dello spazio dei nomi, due punti e un nome, ad esempio: d:testo. +cmm.rest_api.properties_empty_null=Le propriet\u00e0 non possono essere vuote o nulle. +cmm.rest_api.property_create_name_already_in_use=Impossibile creare la propriet\u00e0 perch\u00e9 il nome propriet\u00e0 ''{0}'' \u00e8 gi\u00e0 in uso. +cmm.rest_api.property_update_prop_not_found=Nessuna propriet\u00e0 trovata che corrisponda a ''{0}''. +cmm.rest_api.property_change_datatype_err=Non \u00e8 possibile modificare il tipo di dati di una propriet\u00e0 in un modello attivo. +cmm.rest_api.property_change_mandatory_opt_err=Non \u00e8 possibile modificare l'opzione obbligatoria di una propriet\u00e0 in un modello attivo. +cmm.rest_api.property_change_mandatory_enforced_opt_err=Non \u00e8 possibile modificare l'opzione obbligatoria (imposta) di una propriet\u00e0 in un modello attivo. +cmm.rest_api.property_change_multi_valued_opt_err=Non \u00e8 possibile modificare l'opzione multivalore di una propriet\u00e0 in un modello attivo. +# validation +cmm.rest_api.input_validation_err=''{0}'' non \u00e8 un valore valido. Utilizzare solo numeri, lettere, trattini (-) e caratteri di sottolineatura (_). +cmm.rest_api.prefix_not_registered=Non esiste un prefisso dello spazio dei nomi registrato per l''URI ''{0}''. Assicurarsi che il modello sia attivo. +cmm.rest_api.prefixed_qname_invalid=''{0}'' non \u00e8 un valore QName con prefisso valido. {1} +cmm.rest_api.circular_dependency_err=\u00c8 stata rilevata una dipendenza circolare. Non \u00e8 possibile impostare il genitore''{0}'' perch\u00e9 il modello associato dipende gi\u00e0 da ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' non \u00e8 un formato QName con prefisso valido. Un formato valido deve essere costituito da un prefisso dello spazio dei nomi, due punti e un nome, ad esempio: d:contenuto. +# model import +cmm.rest_api.model.import_namespace_multiple_found=Il modello personalizzato pu\u00f2 avere solo uno spazio dei nomi. \u00c8 stato trovato''{0}''. +cmm.rest_api.model.import_namespace_undefined=Il modello personalizzato deve definire uno spazio dei nomi. +cmm.rest_api.model.import_associations_unsupported=Il modello personalizzato non supporta l'elemento ''associations''. +cmm.rest_api.model.import_overrides_unsupported=Il modello personalizzato non supporta l'elemento ''overrides''. +cmm.rest_api.model.import_mandatory_aspects_unsupported=Il modello personalizzato non supporta l'elemento ''mandatory-aspects''. +cmm.rest_api.model.import_archive_unsupported=Il modello personalizzato non supporta l'elemento ''archive''. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=Il modello personalizzato non supporta l'elemento ''includedInSuperTypeQuery''. +cmm.rest_api.model.import_not_multi_part_req=La richiesta non rappresenta dati di un modulo a pi\u00f9 parti. +cmm.rest_api.model.import_not_zip_format=''{0}'' non \u00e8 un file zip valido. +cmm.rest_api.model.import_process_zip_file_failure=Impossibile elaborare il file zip. +cmm.rest_api.model.import_no_zip_file_uploaded=Impossibile caricare il file zip. +cmm.rest_api.model.import_invalid_zip_package=Il file zip non pu\u00f2 contenere pi\u00f9 di due file. Deve contenere un file Modello e un file Estensione. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' non \u00e8 un modello o un file modulo di estensione di Share. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' non \u00e8 un file modello valido. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}''non \u00e8 un file modulo di estensione di Share valido. +cmm.rest_api.model.import_failure=Impossibile importare il modello. +cmm.rest_api.model.import_process_ext_module_file_failure=Impossibile elaborare il file modulo di estensione di Share. diff --git a/config/alfresco/messages/custommodel-restapi-messages_ja.properties b/config/alfresco/messages/custommodel-restapi-messages_ja.properties new file mode 100644 index 0000000000..e31769adfe --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_ja.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=\u30e2\u30c7\u30eb\u540d\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model_invalid=\u7121\u52b9\u306a\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u3067\u3059\u3002 +cmm.rest_api.model_status_null=\u30e2\u30c7\u30eb\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model_name_cannot_update=\u30e2\u30c7\u30eb\u540d\u3092\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model_update_failure=\u30e2\u30c7\u30eb\u3092\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.model_download_failure=\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u30ce\u30fc\u30c9\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.model_namespace_uri_null=\u30e2\u30c7\u30eb\u306e\u540d\u524d\u7a7a\u9593 URI \u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model_namespace_uri_invalid=\u540d\u524d\u7a7a\u9593 URI \u304c\u7121\u52b9\u3067\u3059\u3002 \u82f1\u5b57\u3001\u6570\u5b57\u3001URI \u6587\u5b57\u3060\u3051\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +cmm.rest_api.model_namespace_prefix_null=\u30e2\u30c7\u30eb\u306e\u540d\u524d\u7a7a\u9593\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +# Type +cmm.rest_api.type_name_null=\u30bf\u30a4\u30d7\u540d\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.type_create_failure=\u30bf\u30a4\u30d7\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.type_update_failure=\u30bf\u30a4\u30d7\u3092\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.type_parent_cannot_update=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u306e\u89aa\u30bf\u30a4\u30d7\u306f\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.type_parent_not_exist=\u30bf\u30a4\u30d7 ''{0}'' \u306f\u5b58\u5728\u3057\u306a\u3044\u305f\u3081\u3001''{0}'' \u306b\u89aa\u30bf\u30a4\u30d7\u306f\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.type_cannot_delete=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u306e\u30bf\u30a4\u30d7\u306f\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.type_delete_failure=\u30bf\u30a4\u30d7\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +# Aspect +cmm.rest_api.aspect_name_null=\u30a2\u30b9\u30da\u30af\u30c8\u540d\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.aspect_create_failure=\u30a2\u30b9\u30da\u30af\u30c8\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.aspect_update_failure=\u30a2\u30b9\u30da\u30af\u30c8\u3092\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.aspect_parent_cannot_update=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u306e\u89aa\u30a2\u30b9\u30da\u30af\u30c8\u306f\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.aspect_parent_not_exist=\u30a2\u30b9\u30da\u30af\u30c8 ''{0}'' \u306f\u5b58\u5728\u3057\u306a\u3044\u305f\u3081\u3001''{0}'' \u306b\u89aa\u30a2\u30b9\u30da\u30af\u30c8\u306f\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.aspect_cannot_delete=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u306e\u30a2\u30b9\u30da\u30af\u30c8\u306f\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.aspect_delete_failure=\u30a2\u30b9\u30da\u30af\u30c8\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=''{0}'' \u306f ''{1}'' \u306e\u4f9d\u5b58\u5148\u3067\u3042\u308b\u305f\u3081\u3001\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 +# Constraint +cmm.rest_api.constraint_name_null=\u5236\u7d04\u540d\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.constraint_create_failure=\u5236\u7d04\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.constraint_parameter_name_null=\u5236\u7d04\u30d1\u30e9\u30e1\u30fc\u30bf\u540d\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.constraint_type_null=\u30ab\u30b9\u30bf\u30e0\u306e\u5236\u7d04\u306b\u306f 'type' \u5c5e\u6027\u304c\u5fc5\u8981\u3067\u3059\u3002 +cmm.rest_api.constraint_ref_not_defined=\u5236\u7d04\u306e\u53c2\u7167 ''{0}'' \u306f\u3001\u3053\u306e\u30e2\u30c7\u30eb\u3067\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +cmm.rest_api.regex_constraint_invalid_expression=REGEX \u8868\u73fe ''{0}'' \u304c\u7121\u52b9\u3067\u3059\u3002 +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' \u306f MINMAX \u30d1\u30e9\u30e1\u30fc\u30bf ''{1}'' \u306e\u6709\u52b9\u306a\u500d\u7cbe\u5ea6\u5024\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.minmax_constraint_invalid_max_value=MINMAX \u5236\u7d04\u306e\u6700\u5927\u5024\u306f\u3001\u30bc\u30ed\u3088\u308a\u5927\u304d\u304f\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.minmax_constraint_invalid_use=MINMAX \u5236\u7d04\u306f\u3001\u6570\u5b57\u306e\u30c7\u30fc\u30bf\u578b\u3067\u3057\u304b\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' \u306f\u3001LENGTH \u30d1\u30e9\u30e1\u30fc\u30bf ''{1}'' \u306e\u6709\u52b9\u306a\u6574\u6570\u5024\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.length_constraint_invalid_use=LENGTH \u5236\u7d04\u306f\u3001TEXT\u3001CONTENT\u3001MLTEXT \u306e\u30c7\u30fc\u30bf\u578b\u3067\u3057\u304b\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +# Property +cmm.rest_api.property_name_null=\u30d7\u30ed\u30d1\u30c6\u30a3\u540d\u306f\u30cc\u30eb\u306b\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.property_create_update_failure=\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u4f5c\u6210\u307e\u305f\u306f\u66f4\u65b0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.property_delete_failure=\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.property_datatype_invalid=''{0}'' \u306f\u6709\u52b9\u306a\u30c7\u30fc\u30bf\u578b\u5f62\u5f0f\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 \u6709\u52b9\u306a\u5f62\u5f0f\u306f\u3001\u540d\u524d\u7a7a\u9593\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3001\u30b3\u30ed\u30f3 (:)\u3001\u540d\u524d\u306e\u7d44\u307f\u5408\u308f\u305b\u3067\u3059\u3002\u4f8b: d:text +cmm.rest_api.properties_empty_null=\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u7a7a\u307e\u305f\u306f\u30cc\u30eb\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.property_create_name_already_in_use=''{0}'' \u3068\u3044\u3046\u30d7\u30ed\u30d1\u30c6\u30a3\u540d\u306f\u3059\u3067\u306b\u4f7f\u308f\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30d7\u30ed\u30d1\u30c6\u30a3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.property_update_prop_not_found=''{0}'' \u3068\u4e00\u81f4\u3059\u308b\u30d7\u30ed\u30d1\u30c6\u30a3\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.property_change_datatype_err=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u30c7\u30fc\u30bf\u578b\u306f\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.property_change_mandatory_opt_err=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u5fc5\u9808\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.property_change_mandatory_enforced_opt_err=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u306e\u5fc5\u9808\u81ea\u52d5\u5165\u529b\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.property_change_multi_valued_opt_err=\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u30e2\u30c7\u30eb\u306e\u30d7\u30ed\u30d1\u30c6\u30a3\u306e\u8907\u6570\u5024\u30aa\u30d7\u30b7\u30e7\u30f3\u306f\u5909\u66f4\u3067\u304d\u307e\u305b\u3093\u3002 +# validation +cmm.rest_api.input_validation_err=''{0}'' \u306f\u6709\u52b9\u306a\u5024\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 \u6570\u5b57\u3001\u82f1\u5b57\u3001\u30cf\u30a4\u30d5\u30f3 (-)\u3001\u30a2\u30f3\u30c0\u30fc\u30b9\u30b3\u30a2 (_) \u3060\u3051\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +cmm.rest_api.prefix_not_registered=URI ''{0}'' \u7528\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u540d\u524d\u7a7a\u9593\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 \u30e2\u30c7\u30eb\u304c\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +cmm.rest_api.prefixed_qname_invalid=''{0}'' \u306f\u6709\u52b9\u306a\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u4ed8\u304d QName \u5024\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 {1} +cmm.rest_api.circular_dependency_err=\u5faa\u74b0\u4f9d\u5b58\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f\u3002 ''{0}'' \u306f\u3059\u3067\u306b ''{1}'' \u306b\u4f9d\u5b58\u3059\u308b\u30e2\u30c7\u30eb\u3067\u3042\u308b\u305f\u3081\u3001\u89aa\u3092\u8a2d\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' \u306f\u6709\u52b9\u306a\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u4ed8\u304d QName \u5f62\u5f0f\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 \u6709\u52b9\u306a\u5f62\u5f0f\u306f\u3001\u540d\u524d\u7a7a\u9593\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3001\u30b3\u30ed\u30f3 (:)\u3001\u540d\u524d\u306e\u7d44\u307f\u5408\u308f\u305b\u3067\u3059\u3002\u4f8b: cm:content +# model import +cmm.rest_api.model.import_namespace_multiple_found=\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u306b\u5b9a\u7fa9\u3067\u304d\u308b\u540d\u524d\u7a7a\u9593\u306f 1 \u3064\u3060\u3051\u3067\u3059\u304c\u3001 ''{0}'' \u500b\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002 +cmm.rest_api.model.import_namespace_undefined=\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u306b\u540d\u524d\u7a7a\u9593\u3092 1 \u3064\u5b9a\u7fa9\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 +cmm.rest_api.model.import_associations_unsupported=\u3053\u306e\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u3067\u306f 'associations' \u8981\u7d20\u3092\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_overrides_unsupported=\u3053\u306e\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u3067\u306f 'overrides' \u8981\u7d20\u3092\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_mandatory_aspects_unsupported=\u3053\u306e\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u3067\u306f 'mandatory-aspects' \u8981\u7d20\u3092\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_archive_unsupported=\u3053\u306e\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u3067\u306f 'archive' \u8981\u7d20\u3092\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_includedInSuperTQ_unsupported=\u3053\u306e\u30ab\u30b9\u30bf\u30e0\u30e2\u30c7\u30eb\u3067\u306f 'includedInSuperTypeQuery' \u8981\u7d20\u3092\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_not_multi_part_req=\u30ea\u30af\u30a8\u30b9\u30c8\u306f\u30de\u30eb\u30c1\u30d1\u30fc\u30c8\u306e\u30d5\u30a9\u30fc\u30e0\u30c7\u30fc\u30bf\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_not_zip_format=''{0}'' \u306f\u6709\u52b9\u306a zip \u30d5\u30a1\u30a4\u30eb\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_process_zip_file_failure=zip \u30d5\u30a1\u30a4\u30eb\u3092\u51e6\u7406\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.model.import_no_zip_file_uploaded=zip \u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.model.import_invalid_zip_package=\u3053\u306e zip \u30d5\u30a1\u30a4\u30eb\u306b 3 \u3064\u4ee5\u4e0a\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u542b\u3081\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 \u30e2\u30c7\u30eb\u30d5\u30a1\u30a4\u30eb\u3068\u62e1\u5f35\u30e2\u30b8\u30e5\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u3092\u305d\u308c\u305e\u308c 1 \u3064\u305a\u3064\u542b\u3081\u308b\u3088\u3046\u306b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' \u306f\u30e2\u30c7\u30eb\u30d5\u30a1\u30a4\u30eb\u307e\u305f\u306f Share \u62e1\u5f35\u30e2\u30b8\u30e5\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_invalid_model_entry=''{0}'' \u306f\u6709\u52b9\u306a\u30e2\u30c7\u30eb\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' \u306f\u6709\u52b9\u306a Share \u62e1\u5f35\u30e2\u30b8\u30e5\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 +cmm.rest_api.model.import_failure=\u30e2\u30c7\u30eb\u3092\u30a4\u30f3\u30dd\u30fc\u30c8\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +cmm.rest_api.model.import_process_ext_module_file_failure=Share \u62e1\u5f35\u30e2\u30b8\u30e5\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u3092\u51e6\u7406\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 diff --git a/config/alfresco/messages/custommodel-restapi-messages_nb.properties b/config/alfresco/messages/custommodel-restapi-messages_nb.properties new file mode 100644 index 0000000000..14807a2d72 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_nb.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=Modellnavn kan ikke v\u00e6re null. +cmm.rest_api.model_invalid=Ugyldig tilpasset modell. +cmm.rest_api.model_status_null=Modellstatus kan ikke v\u00e6re null. +cmm.rest_api.model_name_cannot_update=Modellnavnet kan ikke oppdateres. +cmm.rest_api.model_update_failure=Vi kan ikke oppdatere modellen. +cmm.rest_api.model_download_failure=Vi kan ikke opprette en nedlastingsnode. +cmm.rest_api.model_namespace_uri_null=Modellens URI for navneomr\u00e5det kan ikke v\u00e6re null. +cmm.rest_api.model_namespace_uri_invalid=URI for navneomr\u00e5det er ugyldig. Bruk kun bokstaver, tall og URI-tegn. +cmm.rest_api.model_namespace_prefix_null=Modellens prefiks for navneomr\u00e5det kan ikke v\u00e6re null. +# Type +cmm.rest_api.type_name_null=Type navn kan ikke v\u00e6re null. +cmm.rest_api.type_create_failure=Vi kan ikke opprette typen. +cmm.rest_api.type_update_failure=Vi kan ikke oppdatere typen. +cmm.rest_api.type_parent_cannot_update=Du kan ikke oppdatere typens overordnede i en aktiv, tilpasset modell. +cmm.rest_api.type_parent_not_exist=Du kan ikke angi typens overordnede som ''{0}'', fordi typen ''{0}'' finnes ikke. +cmm.rest_api.type_cannot_delete=En type i en aktiv modell kan ikke slettes. +cmm.rest_api.type_delete_failure=Vi kan ikke slette typen. +# Aspect +cmm.rest_api.aspect_name_null=Aspektnavn kan ikke v\u00e6re null. +cmm.rest_api.aspect_create_failure=Vi kan ikke opprette aspektet. +cmm.rest_api.aspect_update_failure=Vi kan ikke oppdatere aspektet. +cmm.rest_api.aspect_parent_cannot_update=Du kan ikke oppdatere det aspektets overordnede i en aktiv, tilpasset modell. +cmm.rest_api.aspect_parent_not_exist=Du kan ikke angi aspektets overordnede til ''{0}'', fordi aspektet ''{0}'' finnes ikke. +cmm.rest_api.aspect_cannot_delete=Et aspekt i en aktiv modell kan ikke slettes. +cmm.rest_api.aspect_delete_failure=Vi kan ikke slette aspektet. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=''{0}'' kan ikke slettes fordi ''{1}'' er avhengig av den. +# Constraint +cmm.rest_api.constraint_name_null=Restriksjonsnavn kan ikke v\u00e6re null. +cmm.rest_api.constraint_create_failure=Vi kan ikke opprette restriksjon. +cmm.rest_api.constraint_parameter_name_null=Restriksjonsparameternavn kan ikke v\u00e6re null. +cmm.rest_api.constraint_type_null=Tilpasset restriksjon m\u00e5 ha en 'type' attributt. +cmm.rest_api.constraint_ref_not_defined=Restriksjonsreferansen ''{0}'' defineres ikke av denne modellen. +cmm.rest_api.regex_constraint_invalid_expression=REGEX-uttrykket ''{0}'' er ikke gyldig. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' er ikke et gyldig ''dobbel'' verdi for MINMAX-parameteret ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=Maksimalverdien til MINMAX-restriksjonen m\u00e5 v\u00e6re st\u00f8rre enn null. +cmm.rest_api.minmax_constraint_invalid_use=MINMAX-restriksjon kan bare brukes med nummerisk datatype. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' er ikke et gyldig ''helt tall'' for LENGTH-parameteret ''{1}''. +cmm.rest_api.length_constraint_invalid_use=Kun LENGTH-restriksjonen kan brukes sammen med TEXT-, CONTENT- og MLTEXT-datatyper. +# Property +cmm.rest_api.property_name_null=Egenskapsnavn kan ikke v\u00e6re null. +cmm.rest_api.property_create_update_failure=Vi kan ikke opprette/oppdatere egenskap. +cmm.rest_api.property_delete_failure=Vi kan ikke slette egenskapen.. +cmm.rest_api.property_datatype_invalid=''{0}'' er ikke gyldig datatypeformat. Et gyldig format skal best\u00e5 av prefiks for til navneomr\u00e5de, kolon og et navn, som f.eks: d:tekst. +cmm.rest_api.properties_empty_null=Egenskapene kan ikke v\u00e6re tomme eller null. +cmm.rest_api.property_create_name_already_in_use=Vi kan ikke opprette egenskapen fordi egenskapsnavnet ''{0}'' er allerede i bruk. +cmm.rest_api.property_update_prop_not_found=Vi kan ikke finne en egenskap som stemmer overens med ''{0}''. +cmm.rest_api.property_change_datatype_err=Du kan ikke endre datatypen til en egenskap i en aktiv modell. +cmm.rest_api.property_change_mandatory_opt_err=Du kan ikke endre det obligatoriske alternativet til en egenskap i en aktiv modell. +cmm.rest_api.property_change_mandatory_enforced_opt_err=Du kan ikke endre det obligatoriske, h\u00e5ndhevede alternativet til en egenskap i en aktiv modell. +cmm.rest_api.property_change_multi_valued_opt_err=Du kan ikke endre alternativet med flere verdier til en egenskap i en aktiv modell. +# validation +cmm.rest_api.input_validation_err=''{0}'' er ikke en gyldig verdi. Bruk kun tall, bokstaver, bindestreker (-) og understreking (_). +cmm.rest_api.prefix_not_registered=Det finnes ikke en prefiks for navneomr\u00e5det som er registrert for URI-en ''{0}''. Kontroller at modellen er aktiv. +cmm.rest_api.prefixed_qname_invalid=''{0}'' er ikke en gyldig forh\u00e5ndsbestemt QName-verdi. {1} +cmm.rest_api.circular_dependency_err=En det funnet en sirkul\u00e6r avhengighet. Du kan ikke angi overordnede ''{0}'' fordi dens modell er allerede avhengig av ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' er ikke et gyldig forh\u00e5ndsbestemt QName-format. Et gyldig format skal best\u00e5 av en prefiks for navneomr\u00e5det, et kolon og et navn, som f.eks: cm:innhold. +# model import +cmm.rest_api.model.import_namespace_multiple_found=Den tilpassede modellen kan bare ha ett navneomr\u00e5de. Vi fant ''{0}''. +cmm.rest_api.model.import_namespace_undefined=Den tilpassede modellen m\u00e5 definere et navneomr\u00e5de. +cmm.rest_api.model.import_associations_unsupported=Den tilpassede modellen st\u00f8tter ikke elementet 'associations'. +cmm.rest_api.model.import_overrides_unsupported=Den tilpassede modellen st\u00f8tter ikke elementet 'overrides'. +cmm.rest_api.model.import_mandatory_aspects_unsupported=Den tilpassed modellen st\u00f8tter ikke elementet 'mandatory-aspects'. +cmm.rest_api.model.import_archive_unsupported=Den tilpassede modellen st\u00f8tter ikke elementet 'archive'. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=Den tilpassede modellen st\u00f8tter ikke elementet 'includedInSuperTypeQuery'. +cmm.rest_api.model.import_not_multi_part_req=Foresp\u00f8rselen har data med flere deler. +cmm.rest_api.model.import_not_zip_format=''{0}'' er ikke gyldig zip-format. +cmm.rest_api.model.import_process_zip_file_failure=Vi kan ikke behandle zip-filen. +cmm.rest_api.model.import_no_zip_file_uploaded=Vi kan ikke laste opp zip-filen. +cmm.rest_api.model.import_invalid_zip_package=Zip-filen kan ikke ha flere enn to filer. Det skal v\u00e6re \u00e9n modellfil og \u00e9n utvidelsesmodulfil. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' er ikke en modell eller Share-utvidelsesmodulfil. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' er ikke en gyldig modellfil. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' er ikke en gyldig Share-utvidelsesmodulfil. +cmm.rest_api.model.import_failure=Vi kan ikke importere modellen. +cmm.rest_api.model.import_process_ext_module_file_failure=Vi kan ikke behandle Share-utvidelsesmodulfilen. diff --git a/config/alfresco/messages/custommodel-restapi-messages_nl.properties b/config/alfresco/messages/custommodel-restapi-messages_nl.properties new file mode 100644 index 0000000000..836e39ac51 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_nl.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=Modelnaam kan niet null zijn. +cmm.rest_api.model_invalid=Ongeldig aangepast model. +cmm.rest_api.model_status_null=Modelstatus kan niet null zijn. +cmm.rest_api.model_name_cannot_update=U kunt de modelnaam niet bijwerken. +cmm.rest_api.model_update_failure=Kan het model niet bijwerken. +cmm.rest_api.model_download_failure=Kan geen downloadnode maken. +cmm.rest_api.model_namespace_uri_null=De modelnaamruimte-URI kan niet null zijn. +cmm.rest_api.model_namespace_uri_invalid=De naamruimte-URI is ongeldig. Gebruik alleen letters, cijfers en URI-tekens. +cmm.rest_api.model_namespace_prefix_null=Modelnaamruimtevoorvoegsel kan niet null zijn. +# Type +cmm.rest_api.type_name_null=Typenaam kan niet null zijn. +cmm.rest_api.type_create_failure=Kan het type niet maken. +cmm.rest_api.type_update_failure=Kan het type niet bijwerken. +cmm.rest_api.type_parent_cannot_update=U kunt het bovenliggende type van een type in een actief aangepast model niet bijwerken. +cmm.rest_api.type_parent_not_exist=U kunt het bovenliggende type van het type niet instellen op ''{0}'' omdat het type ''{0}'' niet bestaat. +cmm.rest_api.type_cannot_delete=U kunt een type in een actief model niet verwijderen. +cmm.rest_api.type_delete_failure=Kan het type niet verwijderen. +# Aspect +cmm.rest_api.aspect_name_null=Aspectnaam kan niet null zijn. +cmm.rest_api.aspect_create_failure=Kan het aspect niet maken. +cmm.rest_api.aspect_update_failure=Kan het aspect niet bijwerken. +cmm.rest_api.aspect_parent_cannot_update=U kunt het bovenliggende aspect van het aspect in een actief aangepast model niet bijwerken. +cmm.rest_api.aspect_parent_not_exist=U kunt het bovenliggende aspect van het aspect niet instellen op ''{0}'' omdat het aspect ''{0}'' niet bestaat. +cmm.rest_api.aspect_cannot_delete=U kunt een aspect in een actief model niet verwijderen. +cmm.rest_api.aspect_delete_failure=Kan het aspect niet verwijderen. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=U kunt ''{0}'' niet verwijderen omdat ''{1}'' hiervan afhankelijk is. +# Constraint +cmm.rest_api.constraint_name_null=Beperkingsnaam kan niet null zijn. +cmm.rest_api.constraint_create_failure=Kan beperking niet maken. +cmm.rest_api.constraint_parameter_name_null=Beperkingsparameter kan niet null zijn. +cmm.rest_api.constraint_type_null=Aangepaste beperking moet een 'type'-kenmerk hebben. +cmm.rest_api.constraint_ref_not_defined=Beperkingsverwijzing ''{0}'' wordt niet gedefinieerd door dit model. +cmm.rest_api.regex_constraint_invalid_expression=REGEX-expressie ''{0}'' is niet geldig. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' is geen geldige ''dubbele'' waarde voor de MINMAX-parameter ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=De maximale waarde van de MINMAX-beperking moet hoger zijn dan nul. +cmm.rest_api.minmax_constraint_invalid_use=MINMAX-beperking kan alleen worden gebruikt met een numeriek gegevenstype. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' is geen geldige ''integer''-waarde voor de LENGTH-parameter ''{1}''. +cmm.rest_api.length_constraint_invalid_use=U kunt de LENGTH-beperking alleen gebruiken met TEXT-, CONTENT- en MLTEXT-gegevenstypen. +# Property +cmm.rest_api.property_name_null=Eigenschapnaam kan niet null zijn. +cmm.rest_api.property_create_update_failure=Kan de eigenschap niet maken/bijwerken. +cmm.rest_api.property_delete_failure=Kan de eigenschap niet verwijderen. +cmm.rest_api.property_datatype_invalid=''{0}'' is geen geldige gegevenstype-indeling. Een geldige indeling moet bestaan uit een naamruimtevoorvoegsel, een dubbele punt en een naam, bijvoorbeeld: d:tekst. +cmm.rest_api.properties_empty_null=Eigenschappen kunnen niet leeg of null zijn. +cmm.rest_api.property_create_name_already_in_use=Kan de eigenschap niet maken omdat de eigenschapsnaam ''{0}'' al in gebruik is. +cmm.rest_api.property_update_prop_not_found=Kan geen eigenschap vinden dat overeenkomt met ''{0}''. +cmm.rest_api.property_change_datatype_err=U kunt het gegevenstype van een eigenschap in een actief model niet wijzigen. +cmm.rest_api.property_change_mandatory_opt_err=U kunt de verplichte optie van een eigenschap in een actief model niet wijzigen. +cmm.rest_api.property_change_mandatory_enforced_opt_err=U kunt de verplichte-afgedwongen optie van een eigenschap in een actief model niet wijzigen. +cmm.rest_api.property_change_multi_valued_opt_err=U kunt de meerwaardige optie van een eigenschap in een actief model niet wijzigen. +# validation +cmm.rest_api.input_validation_err=''{0}'' is geen geldige waarde. Gebruik alleen cijfers, letters, afbreekstreepjes (-) en onderstrepingstekens (_). +cmm.rest_api.prefix_not_registered=Er is geen naamruimtevoorvoegsel geregistreerd voor de URI ''{0}''. Controleer of het model actief is. +cmm.rest_api.prefixed_qname_invalid=''{0}'' is geen geldige waarde voor QName met voorvoegsel. {1} +cmm.rest_api.circular_dependency_err=Er is een circulaire afhankelijkheid vastgesteld. U kunt het bovenliggende element ''{0}'' niet instellen, omdat het bijbehorende model al afhankelijk is van ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' is geen geldige indeling voor QName met voorvoegsel. Een geldige indeling moet bestaan uit een naamruimtevoorvoegsel, een dubbele punt en een naam, bijvoorbeeld: cm:content. +# model import +cmm.rest_api.model.import_namespace_multiple_found=Het aangepaste model kan slechts \u00e9\u00e9n naamruimte hebben. ''{0}'' gevonden. +cmm.rest_api.model.import_namespace_undefined=Het aangepaste model moet een naamruimte defini\u00ebren. +cmm.rest_api.model.import_associations_unsupported=Het aangepaste model biedt geen ondersteuning voor het element 'associations'. +cmm.rest_api.model.import_overrides_unsupported=Het aangepaste model biedt geen ondersteuning voor het element 'overrides'. +cmm.rest_api.model.import_mandatory_aspects_unsupported=Het aangepaste model biedt geen ondersteuning voor het element 'mandatory-aspects'. +cmm.rest_api.model.import_archive_unsupported=Het aangepaste model biedt geen ondersteuning voor het element 'archive'. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=Het aangepaste model biedt geen ondersteuning voor het element 'includedInSuperTypeQuery'. +cmm.rest_api.model.import_not_multi_part_req=Aanvraag betreft geen meerdelige-formuliergegevens. +cmm.rest_api.model.import_not_zip_format=''{0}'' is geen geldig .zip-bestand. +cmm.rest_api.model.import_process_zip_file_failure=Kan het .zip-bestand niet verwerken. +cmm.rest_api.model.import_no_zip_file_uploaded=Kan het .zip-bestand niet uploaden. +cmm.rest_api.model.import_invalid_zip_package=Het .zip-bestand kan niet meer dan twee bestanden bevatten. Er moet \u00e9\u00e9n modelbestand en \u00e9\u00e9n extensiemodulebestand zijn. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' is geen modelbestand of een Share-extensiemodulebestand. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' is geen geldig modelbestand. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' is geen Share-extensiemodulebestand. +cmm.rest_api.model.import_failure=Kan het model niet importeren. +cmm.rest_api.model.import_process_ext_module_file_failure=Kan het Share-extensiemodulebestand niet verwerken. diff --git a/config/alfresco/messages/custommodel-restapi-messages_pt_BR.properties b/config/alfresco/messages/custommodel-restapi-messages_pt_BR.properties new file mode 100644 index 0000000000..58fca4229a --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_pt_BR.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=O nome do modelo n\u00e3o pode ser nulo. +cmm.rest_api.model_invalid=Modelo personalizado inv\u00e1lido. +cmm.rest_api.model_status_null=O status do modelo n\u00e3o pode ser nulo. +cmm.rest_api.model_name_cannot_update=N\u00e3o \u00e9 poss\u00edvel atualizar o nome do modelo. +cmm.rest_api.model_update_failure=N\u00e3o foi poss\u00edvel atualizar o modelo. +cmm.rest_api.model_download_failure=N\u00e3o foi poss\u00edvel criar um n\u00f3 de download. +cmm.rest_api.model_namespace_uri_null=O URI do espa\u00e7o de nomes do modelo n\u00e3o pode ser nulo. +cmm.rest_api.model_namespace_uri_invalid=O URI do espa\u00e7o de nomes \u00e9 inv\u00e1lido. Use apenas n\u00fameros, letras e caracteres URI. +cmm.rest_api.model_namespace_prefix_null=O prefixo do espa\u00e7o de nomes do modelo n\u00e3o pode ser nulo. +# Type +cmm.rest_api.type_name_null=O nome do tipo n\u00e3o pode ser nulo. +cmm.rest_api.type_create_failure=N\u00e3o foi poss\u00edvel criar o tipo. +cmm.rest_api.type_update_failure=N\u00e3o foi poss\u00edvel atualizar o tipo. +cmm.rest_api.type_parent_cannot_update=N\u00e3o \u00e9 poss\u00edvel atualizar o tipo principal em um modelo personalizado ativo. +cmm.rest_api.type_parent_not_exist=N\u00e3o \u00e9 poss\u00edvel configurar o tipo principal como ''{0}'', porque o tipo ''{0}'' n\u00e3o existe. +cmm.rest_api.type_cannot_delete=N\u00e3o \u00e9 poss\u00edvel excluir um tipo em um modelo ativo. +cmm.rest_api.type_delete_failure=N\u00e3o foi poss\u00edvel excluir o tipo. +# Aspect +cmm.rest_api.aspect_name_null=O nome do aspecto n\u00e3o pode ser nulo. +cmm.rest_api.aspect_create_failure=N\u00e3o foi poss\u00edvel criar o aspecto. +cmm.rest_api.aspect_update_failure=N\u00e3o foi poss\u00edvel atualizar o aspecto. +cmm.rest_api.aspect_parent_cannot_update=N\u00e3o \u00e9 poss\u00edvel atualizar o aspecto principal em um modelo personalizado ativo. +cmm.rest_api.aspect_parent_not_exist=N\u00e3o \u00e9 poss\u00edvel configurar o aspecto principal como ''{0}'', porque o aspecto ''{0}'' n\u00e3o existe. +cmm.rest_api.aspect_cannot_delete=N\u00e3o \u00e9 poss\u00edvel excluir um aspecto em um modelo ativo. +cmm.rest_api.aspect_delete_failure=N\u00e3o foi poss\u00edvel excluir o aspecto. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=N\u00e3o \u00e9 poss\u00edvel excluir ''{0}'', pois ''{1}'' depende dele. +# Constraint +cmm.rest_api.constraint_name_null=O nome da restri\u00e7\u00e3o n\u00e3o pode ser nulo. +cmm.rest_api.constraint_create_failure=N\u00e3o foi poss\u00edvel criar a restri\u00e7\u00e3o. +cmm.rest_api.constraint_parameter_name_null=O par\u00e2metro da restri\u00e7\u00e3o n\u00e3o pode ser nulo. +cmm.rest_api.constraint_type_null=Restri\u00e7\u00e3o personalizada deve conter um atributo de 'tipo'. +cmm.rest_api.constraint_ref_not_defined=A refer\u00eancia de restri\u00e7\u00e3o ''{0}'' n\u00e3o \u00e9 definida por este modelo. +cmm.rest_api.regex_constraint_invalid_expression=A express\u00e3o REGEX ''{0}'' n\u00e3o \u00e9 v\u00e1lida +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' n\u00e3o \u00e9 um valor ''duplo'' v\u00e1lido para o par\u00e2metro MINMAX ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=O valor m\u00e1ximo da restri\u00e7\u00e3o MINMAX deve ser maior que zero. +cmm.rest_api.minmax_constraint_invalid_use=A restri\u00e7\u00e3o MINMAX s\u00f3 pode ser usada com tipo de dados num\u00e9ricos. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' n\u00e3o \u00e9 um valor ''inteiro'' v\u00e1lido para o par\u00e2metro LENGTH ''{1}''. +cmm.rest_api.length_constraint_invalid_use=Voc\u00ea s\u00f3 pode usar a restri\u00e7\u00e3o LENGTH com tipos de dados TEXT, CONTENT e MLTEXT. +# Property +cmm.rest_api.property_name_null=O nome da propriedade n\u00e3o pode ser nulo. +cmm.rest_api.property_create_update_failure=N\u00e3o foi poss\u00edvel criar/atualizar a propriedade. +cmm.rest_api.property_delete_failure=N\u00e3o foi poss\u00edvel excluir a propriedade. +cmm.rest_api.property_datatype_invalid=''{0}'' n\u00e3o \u00e9 um formato de tipo de dados v\u00e1lido. Um formato v\u00e1lido deve conter um prefixo do espa\u00e7o de nomes, dois pontos e um nome, por exemplo: d:texto. +cmm.rest_api.properties_empty_null=As propriedades n\u00e3o podem estar em branco ou ser nulas. +cmm.rest_api.property_create_name_already_in_use=N\u00e3o foi poss\u00edvel criar a propriedade j\u00e1 que o nome da propriedade ''{0}'' j\u00e1 est\u00e1 em uso. +cmm.rest_api.property_update_prop_not_found=N\u00e3o foi poss\u00edvel encontrar uma propriedade que corresponda a ''{0}''. +cmm.rest_api.property_change_datatype_err=N\u00e3o \u00e9 poss\u00edvel alterar o tipo de dados de uma propriedade em um modelo ativo. +cmm.rest_api.property_change_mandatory_opt_err=N\u00e3o \u00e9 poss\u00edvel alterar a op\u00e7\u00e3o obrigat\u00f3ria de uma propriedade em um modelo ativo. +cmm.rest_api.property_change_mandatory_enforced_opt_err=N\u00e3o \u00e9 poss\u00edvel alterar a op\u00e7\u00e3o obrigat\u00f3ria e for\u00e7ada de uma propriedade em um modelo ativo. +cmm.rest_api.property_change_multi_valued_opt_err=N\u00e3o \u00e9 poss\u00edvel alterar a op\u00e7\u00e3o de multivalores de uma propriedade em um modelo ativo. +# validation +cmm.rest_api.input_validation_err=''{0}'' n\u00e3o \u00e9 um valor v\u00e1lido. Use apenas n\u00fameros, letras, h\u00edfen (-) e sublinhado (_). +cmm.rest_api.prefix_not_registered=N\u00e3o h\u00e1 um prefixo do espa\u00e7o de nomes registrado para o URI ''{0}''. Assegure-se de que o modelo esteja ativo. +cmm.rest_api.prefixed_qname_invalid=''{0}'' n\u00e3o \u00e9 um valor de QName com prefixo v\u00e1lido. {1} +cmm.rest_api.circular_dependency_err=Foi detectada uma depend\u00eancia circular. N\u00e3o \u00e9 poss\u00edvel configurar o principal ''{0}'', pois seu modelo j\u00e1 depende de ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' n\u00e3o \u00e9 um formato de QName com prefixo v\u00e1lido. Um formato v\u00e1lido deve conter um prefixo do espa\u00e7o de nomes, dois pontos e um nome, por exemplo: cm:conte\u00fado. +# model import +cmm.rest_api.model.import_namespace_multiple_found=O modelo personalizado s\u00f3 pode ter um espa\u00e7o de nomes. Encontramos ''{0}''. +cmm.rest_api.model.import_namespace_undefined=O modelo personalizado deve definir um espa\u00e7o de nomes. +cmm.rest_api.model.import_associations_unsupported=O modelo personalizado n\u00e3o suporta o elemento 'associations'. +cmm.rest_api.model.import_overrides_unsupported=O modelo personalizado n\u00e3o suporta o elemento 'overrides'. +cmm.rest_api.model.import_mandatory_aspects_unsupported=O modelo personalizado n\u00e3o suporta o elemento 'mandatory-aspects'. +cmm.rest_api.model.import_archive_unsupported=O modelo personalizado n\u00e3o suporta o elemento 'archive'. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=O modelo personalizado n\u00e3o suporta o elemento 'includedInSuperTypeQuery'. +cmm.rest_api.model.import_not_multi_part_req=A solicita\u00e7\u00e3o n\u00e3o \u00e9 um formul\u00e1rio de dados de m\u00faltiplas partes. +cmm.rest_api.model.import_not_zip_format=''{0}'' n\u00e3o \u00e9 um arquivo zip v\u00e1lido. +cmm.rest_api.model.import_process_zip_file_failure=N\u00e3o foi poss\u00edvel processar o arquivo zip. +cmm.rest_api.model.import_no_zip_file_uploaded=N\u00e3o foi poss\u00edvel carregar o arquivo zip. +cmm.rest_api.model.import_invalid_zip_package=O arquivo zip n\u00e3o pode conter mais do que dois arquivos. Deve existir um arquivo Modelo e um arquivo de m\u00f3dulo Extens\u00e3o. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' n\u00e3o \u00e9 um modelo ou arquivo de m\u00f3dulo de extens\u00e3o Share. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' n\u00e3o \u00e9 um arquivo modelo v\u00e1lido. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' n\u00e3o \u00e9 um arquivo de m\u00f3dulo de extens\u00e3o Share v\u00e1lido. +cmm.rest_api.model.import_failure=N\u00e3o foi poss\u00edvel importar o modelo. +cmm.rest_api.model.import_process_ext_module_file_failure=N\u00e3o foi poss\u00edvel processar o arquivo de m\u00f3dulo de extens\u00e3o Share. diff --git a/config/alfresco/messages/custommodel-restapi-messages_ru.properties b/config/alfresco/messages/custommodel-restapi-messages_ru.properties new file mode 100644 index 0000000000..85e516916e --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_ru.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=\u0418\u043c\u044f \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.model_invalid=\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u0430\u044f \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c. +cmm.rest_api.model_status_null=\u0421\u0442\u0430\u0442\u0443\u0441 \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.model_name_cannot_update=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u043c\u044f \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.model_update_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u043c\u044f \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.model_download_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0443\u0437\u0435\u043b \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438. +cmm.rest_api.model_namespace_uri_null=URI \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0438\u043c\u0435\u043d \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.model_namespace_uri_invalid=\u041d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 URI \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0438\u043c\u0435\u043d. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u0443\u043a\u0432\u044b, \u0446\u0438\u0444\u0440\u044b \u0438 \u0441\u0438\u043c\u0432\u043e\u043b\u044b URI. +cmm.rest_api.model_namespace_prefix_null=\u041f\u0440\u0435\u0444\u0438\u043a\u0441 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0438\u043c\u0435\u043d \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +# Type +cmm.rest_api.type_name_null=\u0418\u043c\u044f \u0442\u0438\u043f\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.type_create_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0442\u0438\u043f. +cmm.rest_api.type_update_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0438\u043f. +cmm.rest_api.type_parent_cannot_update=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0442\u0438\u043f \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.type_parent_not_exist=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c ''{0}'' \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0442\u0438\u043f\u0430: \u0442\u0438\u043f ''{0}'' \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442. +cmm.rest_api.type_cannot_delete=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0438\u043f \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.type_delete_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0438\u043f. +# Aspect +cmm.rest_api.aspect_name_null=\u0418\u043c\u044f \u0430\u0441\u043f\u0435\u043a\u0442\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.aspect_create_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0430\u0441\u043f\u0435\u043a\u0442. +cmm.rest_api.aspect_update_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0430\u0441\u043f\u0435\u043a\u0442. +cmm.rest_api.aspect_parent_cannot_update=\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0430\u0441\u043f\u0435\u043a\u0442 \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.aspect_parent_not_exist=\u041d\u0435\u043b\u044c\u0437\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c ''{0}'' \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0430\u0441\u043f\u0435\u043a\u0442\u0430: \u0430\u0441\u043f\u0435\u043a\u0442 ''{0}'' \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442. +cmm.rest_api.aspect_cannot_delete=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0430\u0441\u043f\u0435\u043a\u0442 \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.aspect_delete_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0430\u0441\u043f\u0435\u043a\u0442. +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c ''{0}'', \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 ''{1}'' \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430. +# Constraint +cmm.rest_api.constraint_name_null=\u0418\u043c\u044f \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.constraint_create_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435. +cmm.rest_api.constraint_parameter_name_null=\u0418\u043c\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.constraint_type_null=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0438\u043c\u0435\u0442\u044c \u0430\u0442\u0440\u0438\u0431\u0443\u0442 'type'. +cmm.rest_api.constraint_ref_not_defined=\u0414\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u0430 \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 ''{0}''. +cmm.rest_api.regex_constraint_invalid_expression=\u0420\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0435 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435 REGEX ''{0}'' \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e. +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u0432\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u0434\u0432\u043e\u0439\u043d\u043e\u0439 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 \u0441 \u043f\u043b\u0430\u0432\u0430\u044e\u0449\u0435\u0439 \u0442\u043e\u0447\u043a\u043e\u0439 \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 MINMAX ''{1}''. +cmm.rest_api.minmax_constraint_invalid_max_value=\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f MINMAX \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0443\u043b\u044f. +cmm.rest_api.minmax_constraint_invalid_use=\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 MINMAX \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0441 \u0447\u0438\u0441\u043b\u043e\u0432\u044b\u043c \u0442\u0438\u043f\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0445. +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0446\u0435\u043b\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 LENGTH ''{1}''. +cmm.rest_api.length_constraint_invalid_use=\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 LENGTH \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u0442\u0438\u043f\u0430\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 TEXT, CONTENT \u0438 MLTEXT. +# Property +cmm.rest_api.property_name_null=\u0418\u043c\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c. +cmm.rest_api.property_create_update_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c/\u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e. +cmm.rest_api.property_delete_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e. +cmm.rest_api.property_datatype_invalid=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c \u0442\u0438\u043f\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0438\u043c\u0435\u043d, \u0434\u0432\u043e\u0435\u0442\u043e\u0447\u0438\u044f \u0438 \u0438\u043c\u0435\u043d\u0438. \u041f\u0440\u0438\u043c\u0435\u0440: d:text. +cmm.rest_api.properties_empty_null=\u0414\u043b\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432 \u043d\u0435\u043b\u044c\u0437\u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u0435 \u0438\u043b\u0438 \u043d\u0443\u043b\u0435\u0432\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f. +cmm.rest_api.property_create_name_already_in_use=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e: \u0438\u043c\u044f \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 ''{0}'' \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f. +cmm.rest_api.property_update_prop_not_found=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 ''{0}''. +cmm.rest_api.property_change_datatype_err=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.property_change_mandatory_opt_err=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.property_change_mandatory_enforced_opt_err=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 (\u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439) \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.property_change_multi_valued_opt_err=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043c\u043d\u043e\u0433\u043e\u0437\u043d\u0430\u0447\u043d\u043e\u0441\u0442\u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438. +# validation +cmm.rest_api.input_validation_err=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0438\u0441\u043b\u0430, \u0431\u0443\u043a\u0432\u044b, \u0434\u0435\u0444\u0438\u0441\u044b (-) \u0438 \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u044f (_). +cmm.rest_api.prefix_not_registered=\u0414\u043b\u044f URI ''{0}'' \u043d\u0435\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0438\u043c\u0435\u043d. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u0430\u043a\u0442\u0438\u0432\u043d\u0430. +cmm.rest_api.prefixed_qname_invalid=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c QName \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c. {1} +cmm.rest_api.circular_dependency_err=\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u0446\u0438\u043a\u043b\u0438\u0447\u043d\u0430\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c. \u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0434\u0430\u0442\u044c \u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 ''{0}'', \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0443\u0436\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 ''{1}''. +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u043c QName \u0441 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c. \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u0438\u043c\u0435\u043d, \u0434\u0432\u043e\u0435\u0442\u043e\u0447\u0438\u044f \u0438 \u0438\u043c\u0435\u043d\u0438. \u041f\u0440\u0438\u043c\u0435\u0440: cm:content. +# model import +cmm.rest_api.model.import_namespace_multiple_found=\u0423 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0438\u043c\u0435\u043d. \u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e: ''{0}''. +cmm.rest_api.model.import_namespace_undefined=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u043d\u0430 \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0438\u043c\u0435\u043d. +cmm.rest_api.model.import_associations_unsupported=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 'associations'. +cmm.rest_api.model.import_overrides_unsupported=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 'overrides'. +cmm.rest_api.model.import_mandatory_aspects_unsupported=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 'mandatory-aspects'. +cmm.rest_api.model.import_archive_unsupported=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 'archive'. +cmm.rest_api.model.import_includedInSuperTQ_unsupported=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 'includedInSuperTypeQuery'. +cmm.rest_api.model.import_not_multi_part_req=\u0417\u0430\u043f\u0440\u043e\u0441 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0441\u043e\u0441\u0442\u0430\u0432\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b. +cmm.rest_api.model.import_not_zip_format=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c zip-\u0444\u0430\u0439\u043b\u043e\u043c. +cmm.rest_api.model.import_process_zip_file_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c zip-\u0444\u0430\u0439\u043b. +cmm.rest_api.model.import_no_zip_file_uploaded=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c zip-\u0444\u0430\u0439\u043b. +cmm.rest_api.model.import_invalid_zip_package=zip-\u0444\u0430\u0439\u043b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0434\u0432\u0443\u0445 \u0444\u0430\u0439\u043b\u043e\u0432. \u0414\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b \u043c\u043e\u0434\u0435\u043b\u0438 \u0438 \u043e\u0434\u0438\u043d \u0444\u0430\u0439\u043b \u043c\u043e\u0434\u0443\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f. +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u043e\u043c \u043c\u043e\u0434\u0435\u043b\u0438 \u0438\u043b\u0438 \u0444\u0430\u0439\u043b\u043e\u043c \u043c\u043e\u0434\u0443\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430. +cmm.rest_api.model.import_invalid_model_entry=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0444\u0430\u0439\u043b\u043e\u043c \u043c\u043e\u0434\u0435\u043b\u0438. +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0444\u0430\u0439\u043b\u043e\u043c \u043c\u043e\u0434\u0443\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430. +cmm.rest_api.model.import_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c. +cmm.rest_api.model.import_process_ext_module_file_failure=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0444\u0430\u0439\u043b \u043c\u043e\u0434\u0443\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430. diff --git a/config/alfresco/messages/custommodel-restapi-messages_zh_CN.properties b/config/alfresco/messages/custommodel-restapi-messages_zh_CN.properties new file mode 100644 index 0000000000..956755be87 --- /dev/null +++ b/config/alfresco/messages/custommodel-restapi-messages_zh_CN.properties @@ -0,0 +1,78 @@ +# Messages returned by the CMM Rest API + +# Model +cmm.rest_api.model_name_null=\u6a21\u578b\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.model_invalid=\u81ea\u5b9a\u4e49\u6a21\u578b\u65e0\u6548\u3002 +cmm.rest_api.model_status_null=\u6a21\u578b\u72b6\u6001\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.model_name_cannot_update=\u60a8\u65e0\u6cd5\u66f4\u65b0\u6a21\u578b\u540d\u79f0\u3002 +cmm.rest_api.model_update_failure=\u6211\u4eec\u65e0\u6cd5\u66f4\u65b0\u6a21\u578b\u3002 +cmm.rest_api.model_download_failure=\u6211\u4eec\u65e0\u6cd5\u521b\u5efa\u4e0b\u8f7d\u8282\u70b9\u3002 +cmm.rest_api.model_namespace_uri_null=\u6a21\u578b\u547d\u540d\u7a7a\u95f4 URI \u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.model_namespace_uri_invalid=\u547d\u540d\u7a7a\u95f4 URI \u65e0\u6548\u3002 \u4ec5\u53ef\u4f7f\u7528\u5b57\u6bcd\u3001\u6570\u5b57\u548c URI \u5b57\u7b26\u3002 +cmm.rest_api.model_namespace_prefix_null=\u6a21\u578b\u547d\u540d\u7a7a\u95f4\u524d\u7f00\u4e0d\u80fd\u4e3a\u7a7a\u3002 +# Type +cmm.rest_api.type_name_null=\u7c7b\u578b\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.type_create_failure=\u6211\u4eec\u65e0\u6cd5\u521b\u5efa\u7c7b\u578b\u3002 +cmm.rest_api.type_update_failure=\u6211\u4eec\u65e0\u6cd5\u66f4\u65b0\u7c7b\u578b\u3002 +cmm.rest_api.type_parent_cannot_update=\u60a8\u65e0\u6cd5\u5728\u6d3b\u8dc3\u7684\u81ea\u5b9a\u4e49\u6a21\u578b\u4e2d\u66f4\u65b0\u7c7b\u578b\u7684\u7236\u7ea7\u7c7b\u578b\u3002 +cmm.rest_api.type_parent_not_exist=\u60a8\u65e0\u6cd5\u5c06\u7c7b\u578b\u7684\u7236\u7ea7\u7c7b\u578b\u8bbe\u7f6e\u4e3a ''{0}''\uff0c\u56e0\u4e3a\u7c7b\u578b ''{0}'' \u4e0d\u5b58\u5728\u3002 +cmm.rest_api.type_cannot_delete=\u60a8\u65e0\u6cd5\u5220\u9664\u6d3b\u8dc3\u6a21\u578b\u4e2d\u7684\u7c7b\u578b\u3002 +cmm.rest_api.type_delete_failure=\u6211\u4eec\u65e0\u6cd5\u5220\u9664\u7c7b\u578b\u3002 +# Aspect +cmm.rest_api.aspect_name_null=\u5207\u9762\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.aspect_create_failure=\u6211\u4eec\u65e0\u6cd5\u521b\u5efa\u5207\u9762\u3002 +cmm.rest_api.aspect_update_failure=\u6211\u4eec\u65e0\u6cd5\u66f4\u65b0\u5207\u9762\u3002 +cmm.rest_api.aspect_parent_cannot_update=\u60a8\u65e0\u6cd5\u5728\u6d3b\u8dc3\u7684\u81ea\u5b9a\u4e49\u6a21\u578b\u4e2d\u66f4\u65b0\u5207\u9762\u7684\u7236\u7ea7\u5207\u9762\u3002 +cmm.rest_api.aspect_parent_not_exist=\u60a8\u65e0\u6cd5\u5c06\u5207\u9762\u7684\u7236\u7ea7\u5207\u9762\u8bbe\u7f6e\u4e3a ''{0}''\uff0c\u56e0\u4e3a\u5207\u9762 ''{0}'' \u4e0d\u5b58\u5728\u3002 +cmm.rest_api.aspect_cannot_delete=\u60a8\u65e0\u6cd5\u5728\u6d3b\u8dc3\u6a21\u578b\u4e2d\u5220\u9664\u5207\u9762\u3002 +cmm.rest_api.aspect_delete_failure=\u6211\u4eec\u65e0\u6cd5\u5220\u9664\u5207\u9762\u3002 +# TypeAspect +cmm.rest_api.aspect_type_cannot_delete=\u60a8\u65e0\u6cd5\u5220\u9664 ''{0}''\uff0c\u56e0\u4e3a ''{1}'' \u4f9d\u8d56\u4e8e\u5b83\u3002 +# Constraint +cmm.rest_api.constraint_name_null=\u7ea6\u675f\u6761\u4ef6\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.constraint_create_failure=\u6211\u4eec\u65e0\u6cd5\u521b\u5efa\u7ea6\u675f\u6761\u4ef6\u3002 +cmm.rest_api.constraint_parameter_name_null=\u7ea6\u675f\u6761\u4ef6\u53c2\u6570\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.constraint_type_null=\u81ea\u5b9a\u4e49\u7ea6\u675f\u6761\u4ef6\u5fc5\u987b\u6709 '\u7c7b\u578b' \u5c5e\u6027\u3002 +cmm.rest_api.constraint_ref_not_defined=\u6b64\u6a21\u578b\u672a\u5b9a\u4e49\u7ea6\u675f\u6761\u4ef6\u5f15\u7528 ''{0}''\u3002 +cmm.rest_api.regex_constraint_invalid_expression=REGEX \u8868\u8fbe\u5f0f ''{0}'' \u65e0\u6548\u3002 +cmm.rest_api.minmax_constraint_invalid_parameter=''{0}'' \u4e0d\u662f MINMAX \u53c2\u6570 ''{1}'' \u7684\u6709\u6548 ''\u53cc\u7cbe\u5ea6'' \u503c\u3002 +cmm.rest_api.minmax_constraint_invalid_max_value=MINMAX \u7ea6\u675f\u6761\u4ef6\u7684\u6700\u5927\u503c\u5fc5\u987b\u5927\u4e8e\u96f6\u3002 +cmm.rest_api.minmax_constraint_invalid_use=MINMAX \u7ea6\u675f\u6761\u4ef6\u53ea\u80fd\u4e0e\u6570\u5b57\u578b\u6570\u636e\u7ed3\u5408\u4f7f\u7528\u3002 +cmm.rest_api.length_constraint_invalid_parameter=''{0}'' \u4e0d\u662f LENGTH \u53c2\u6570 ''{1}'' \u7684\u6709\u6548 ''\u6574\u6570'' \u503c\u3002 +cmm.rest_api.length_constraint_invalid_use=\u60a8\u53ea\u80fd\u5c06 LENGTH \u7ea6\u675f\u6761\u4ef6\u4e0e TEXT\u3001CONTENT \u548c MLTEXT \u7c7b\u578b\u6570\u636e\u7ed3\u5408\u4f7f\u7528\u3002 +# Property +cmm.rest_api.property_name_null=\u5c5e\u6027\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.property_create_update_failure=\u6211\u4eec\u65e0\u6cd5\u521b\u5efa/\u66f4\u65b0\u5c5e\u6027\u3002 +cmm.rest_api.property_delete_failure=\u6211\u4eec\u65e0\u6cd5\u5220\u9664\u5c5e\u6027\u3002 +cmm.rest_api.property_datatype_invalid=''{0}'' \u4e0d\u662f\u6709\u6548\u7684\u6570\u636e\u7c7b\u578b\u683c\u5f0f\u3002 \u6709\u6548\u7684\u683c\u5f0f\u5e94\u5305\u542b\u547d\u540d\u7a7a\u95f4\u524d\u7f00\u3001\u5192\u53f7\u548c\u540d\u79f0\uff0c\u4f8b\u5982\uff1ad:text\u3002 +cmm.rest_api.properties_empty_null=\u5c5e\u6027\u4e0d\u80fd\u4e3a\u7a7a\u3002 +cmm.rest_api.property_create_name_already_in_use=\u6211\u4eec\u65e0\u6cd5\u521b\u5efa\u5c5e\u6027\uff0c\u56e0\u4e3a\u5c5e\u6027\u540d\u79f0 ''{0}''\u5df2\u5b58\u5728\u3002 +cmm.rest_api.property_update_prop_not_found=\u6211\u4eec\u65e0\u6cd5\u627e\u5230\u4e0e ''{0}'' \u5339\u914d\u7684\u5c5e\u6027\u3002 +cmm.rest_api.property_change_datatype_err=\u60a8\u65e0\u6cd5\u66f4\u6539\u6d3b\u8dc3\u6a21\u578b\u4e2d\u5c5e\u6027\u7684\u6570\u636e\u7c7b\u578b\u3002 +cmm.rest_api.property_change_mandatory_opt_err=\u60a8\u65e0\u6cd5\u66f4\u6539\u6d3b\u8dc3\u6a21\u578b\u4e2d\u5c5e\u6027\u7684\u5f3a\u5236\u9009\u9879\u3002 +cmm.rest_api.property_change_mandatory_enforced_opt_err=\u60a8\u65e0\u6cd5\u66f4\u6539\u6d3b\u8dc3\u6a21\u578b\u4e2d\u5c5e\u6027\u7684\u5f3a\u5236\u6267\u884c\u9009\u9879\u3002 +cmm.rest_api.property_change_multi_valued_opt_err=\u60a8\u65e0\u6cd5\u66f4\u6539\u6d3b\u8dc3\u6a21\u578b\u4e2d\u5c5e\u6027\u7684\u591a\u503c\u9009\u9879\u3002 +# validation +cmm.rest_api.input_validation_err=''{0}'' \u4e0d\u662f\u6709\u6548\u503c\u3002 \u4ec5\u53ef\u4f7f\u7528\u6570\u5b57\u3001\u5b57\u6bcd\u3001\u8fde\u5b57\u7b26 (-) \u548c\u4e0b\u5212\u7ebf (_)\u3002 +cmm.rest_api.prefix_not_registered=\u6ca1\u6709\u4e3a URI ''{0}'' \u6ce8\u518c\u547d\u540d\u7a7a\u95f4\u524d\u7f00\u3002 \u786e\u4fdd\u6a21\u578b\u4e3a\u6d3b\u8dc3\u3002 +cmm.rest_api.prefixed_qname_invalid=''{0}'' \u4e0d\u662f\u52a0\u524d\u7f00\u7684\u6709\u6548 QName \u503c\u3002 {1} +cmm.rest_api.circular_dependency_err=\u68c0\u6d4b\u5230\u5faa\u73af\u4f9d\u5b58\u5173\u7cfb\u3002 \u60a8\u65e0\u6cd5\u8bbe\u7f6e\u7236\u7ea7 ''{0}''\uff0c\u56e0\u4e3a\u5b83\u7684\u6a21\u578b\u5df2\u7ecf\u4f9d\u8d56\u4e8e ''{1}''\u3002 +cmm.rest_api.prefixed_qname_invalid_format=''{0}'' \u4e0d\u662f\u52a0\u524d\u7f00\u7684\u6709\u6548 QName \u683c\u5f0f\u3002 \u6709\u6548\u683c\u5f0f\u5e94\u5305\u542b\u547d\u540d\u7a7a\u95f4\u524d\u7f00\u3001\u5192\u53f7\u548c\u540d\u79f0\uff0c\u4f8b\u5982\uff1acm:content\u3002 +# model import +cmm.rest_api.model.import_namespace_multiple_found=\u81ea\u5b9a\u4e49\u6a21\u578b\u53ea\u80fd\u6709\u4e00\u4e2a\u547d\u540d\u7a7a\u95f4\u3002 \u6211\u4eec\u627e\u5230 ''{0}''\u3002 +cmm.rest_api.model.import_namespace_undefined=\u81ea\u5b9a\u4e49\u6a21\u578b\u5fc5\u987b\u5b9a\u4e49\u4e00\u4e2a\u547d\u540d\u7a7a\u95f4\u3002 +cmm.rest_api.model.import_associations_unsupported=\u81ea\u5b9a\u4e49\u6a21\u578b\u4e0d\u652f\u6301 'associations' \u5143\u7d20\u3002 +cmm.rest_api.model.import_overrides_unsupported=\u81ea\u5b9a\u4e49\u6a21\u578b\u4e0d\u652f\u6301 'overrides' \u5143\u7d20\u3002 +cmm.rest_api.model.import_mandatory_aspects_unsupported=\u81ea\u5b9a\u4e49\u6a21\u578b\u4e0d\u652f\u6301 'mandatory-aspects' \u5143\u7d20\u3002 +cmm.rest_api.model.import_archive_unsupported=\u81ea\u5b9a\u4e49\u6a21\u578b\u4e0d\u652f\u6301 'archive' \u5143\u7d20\u3002 +cmm.rest_api.model.import_includedInSuperTQ_unsupported=\u81ea\u5b9a\u4e49\u6a21\u578b\u4e0d\u652f\u6301 'includedInSuperTypeQuery' \u5143\u7d20\u3002 +cmm.rest_api.model.import_not_multi_part_req=\u8bf7\u6c42\u4e0d\u662f\u591a\u90e8\u5206\u7ec4\u6210\u6570\u636e\u3002 +cmm.rest_api.model.import_not_zip_format=''{0}'' \u4e0d\u662f\u6709\u6548\u7684 zip \u6587\u4ef6\u3002 +cmm.rest_api.model.import_process_zip_file_failure=\u6211\u4eec\u65e0\u6cd5\u5904\u7406 zip \u6587\u4ef6\u3002 +cmm.rest_api.model.import_no_zip_file_uploaded=\u6211\u4eec\u65e0\u6cd5\u4e0a\u4f20 zip \u6587\u4ef6\u3002 +cmm.rest_api.model.import_invalid_zip_package=zip \u6587\u4ef6\u5305\u542b\u7684\u6587\u4ef6\u4e0d\u80fd\u8d85\u8fc7\u4e24\u4e2a\u3002 \u5b83\u4eec\u5e94\u8be5\u4e3a\u4e00\u4e2a\u6a21\u578b\u6587\u4ef6\u548c\u4e00\u4e2a\u6269\u5c55\u6a21\u5757\u6587\u4ef6\u3002 +cmm.rest_api.model.import_invalid_zip_entry_format=''{0}'' \u4e0d\u662f\u6a21\u578b\u6216\u5171\u4eab\u6269\u5c55\u6a21\u5757\u6587\u4ef6\u3002 +cmm.rest_api.model.import_invalid_model_entry=''{0}'' \u4e0d\u662f\u6709\u6548\u7684\u6a21\u578b\u6587\u4ef6\u3002 +cmm.rest_api.model.import_invalid_ext_module_entry=''{0}'' \u4e0d\u662f\u6709\u6548\u7684\u5171\u4eab\u6269\u5c55\u6a21\u5757\u6587\u4ef6\u3002 +cmm.rest_api.model.import_failure=\u6211\u4eec\u65e0\u6cd5\u5bfc\u5165\u6a21\u578b\u3002 +cmm.rest_api.model.import_process_ext_module_file_failure=\u6211\u4eec\u65e0\u6cd5\u5904\u7406\u5171\u4eab\u6269\u5c55\u6a21\u5757\u6587\u4ef6\u3002 diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 24b9753946..4ffbb8cd97 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -102,6 +102,7 @@ alfresco.messages.rest-framework-messages + alfresco.messages.custommodel-restapi-messages @@ -937,4 +938,50 @@ + + + + + + + + + + + + + + + + org.alfresco.rest.api.CustomModels + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.desc.xml new file mode 100644 index 0000000000..41e1990f27 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.desc.xml @@ -0,0 +1,20 @@ + + Custom Model Upload + + :/alfresco/service/api/cmm/upload + +
+ Notes: + - user must be an Admin or a member of the ALFRESCO_MODEL_ADMINISTRATORS group + + ]]> +
+ /api/cmm/upload + + user + required + internal +
\ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.html.status.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.html.status.ftl new file mode 100644 index 0000000000..5cb1b2c125 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.html.status.ftl @@ -0,0 +1,19 @@ + + + Upload Custom Model Failure + + +<#if (args.failure!"")?matches("^[\\w\\d\\._]+$")> + + + + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.json.ftl new file mode 100644 index 0000000000..b505cc606d --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/custommodel/cmm-upload.post.json.ftl @@ -0,0 +1,16 @@ +<#escape x as jsonUtils.encodeJSONString(x)> +{ + <#if importedModelName??> + "modelName": "${importedModelName}", + + <#if shareExtXMLFragment??> + "shareExtModule": "${shareExtXMLFragment}", + + "status": + { + "code": 200, + "name": "OK", + "description": "Model uploaded successfully" + } +} + \ No newline at end of file diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index f11410bf84..5d8807345a 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -1856,5 +1856,12 @@ - + + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/custommodel/CustomModelUploadPost.java b/source/java/org/alfresco/repo/web/scripts/custommodel/CustomModelUploadPost.java new file mode 100644 index 0000000000..97bc90a5c6 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/custommodel/CustomModelUploadPost.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.web.scripts.custommodel; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import javax.xml.parsers.DocumentBuilder; + +import org.alfresco.repo.dictionary.CustomModelServiceImpl; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.dictionary.DictionaryException; +import org.alfresco.util.TempFileProvider; +import org.alfresco.util.XMLUtil; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.servlet.FormData; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +/** + * Custom model upload POST. This class is the controller for the + * "cmm-upload.post" web scripts. + * + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelUploadPost extends DeclarativeWebScript +{ + private static final String SHARE_EXT_MODULE_ROOT_ELEMENT = "module"; + private static final String TEMP_FILE_PREFIX = "cmmExport"; + private static final String TEMP_FILE_SUFFIX = ".zip"; + private static final int BUFFER_SIZE = 10 * 1024; + + private CustomModels customModels; + private CustomModelService customModelService; + + public void setCustomModels(CustomModels customModels) + { + this.customModels = customModels; + } + + public void setCustomModelService(CustomModelService customModelService) + { + this.customModelService = customModelService; + } + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + if (!customModelService.isModelAdmin(AuthenticationUtil.getFullyAuthenticatedUser())) + { + throw new WebScriptException(Status.STATUS_FORBIDDEN, PermissionDeniedException.DEFAULT_MESSAGE_ID); + } + + FormData formData = (FormData) req.parseContent(); + if (formData == null || !formData.getIsMultiPart()) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_not_multi_part_req"); + } + + ImportResult resultData = null; + boolean processed = false; + for (FormData.FormField field : formData.getFields()) + { + if (field.getIsFile()) + { + final String fileName = field.getFilename(); + File tempFile = createTempFile(field.getInputStream()); + try (ZipFile zipFile = new ZipFile(tempFile, StandardCharsets.UTF_8)) + { + resultData = processUpload(zipFile, field.getFilename()); + } + catch (ZipException ze) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_not_zip_format", new Object[] { fileName }); + } + catch (IOException io) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "cmm.rest_api.model.import_process_zip_file_failure", io); + } + finally + { + // now the import is done, delete the temp file + tempFile.delete(); + } + processed = true; + break; + } + + } + + if (!processed) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_no_zip_file_uploaded"); + } + + // If we get here, then importing the custom model didn't throw any exceptions. + Map model = new HashMap<>(2); + model.put("importedModelName", resultData.getImportedModelName()); + model.put("shareExtXMLFragment", resultData.getShareExtXMLFragment()); + + return model; + } + + protected File createTempFile(InputStream inputStream) + { + try + { + File tempFile = TempFileProvider.createTempFile(inputStream, TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX); + return tempFile; + } + catch (Exception ex) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "cmm.rest_api.model.import_process_zip_file_failure", ex); + } + } + + protected ImportResult processUpload(ZipFile zipFile, String filename) throws IOException + { + if (zipFile.size() > 2) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_invalid_zip_package"); + } + + CustomModel customModel = null; + String shareExtModule = null; + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) + { + ZipEntry entry = entries.nextElement(); + + if (!entry.isDirectory()) + { + final String entryName = entry.getName(); + try (InputStream input = new BufferedInputStream(zipFile.getInputStream(entry), BUFFER_SIZE)) + { + if (!(entryName.endsWith(CustomModelServiceImpl.SHARE_EXT_MODULE_SUFFIX)) && customModel == null) + { + try + { + M2Model m2Model = M2Model.createModel(input); + customModel = importModel(m2Model); + } + catch (DictionaryException ex) + { + if (shareExtModule == null) + { + // Get the input stream again, as the zip file doesn't support reset. + try (InputStream moduleInputStream = new BufferedInputStream(zipFile.getInputStream(entry), BUFFER_SIZE)) + { + shareExtModule = getExtensionModule(moduleInputStream, entryName); + } + + if (shareExtModule == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_invalid_zip_entry_format", new Object[] { entryName }); + } + } + else + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_invalid_model_entry", new Object[] { entryName }); + } + } + } + else + { + shareExtModule = getExtensionModule(input, entryName); + if (shareExtModule == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_invalid_ext_module_entry", new Object[] { entryName }); + } + } + } + } + } + + return new ImportResult(customModel, shareExtModule); + } + + protected CustomModel importModel(M2Model m2Model) + { + CustomModel model = null; + try + { + model = customModels.createCustomModel(m2Model); + } + catch (Exception ex) + { + int statusCode; + if (ex instanceof ConstraintViolatedException) + { + statusCode = Status.STATUS_CONFLICT; + } + else if (ex instanceof InvalidArgumentException) + { + statusCode = Status.STATUS_BAD_REQUEST; + } + else + { + statusCode = Status.STATUS_INTERNAL_SERVER_ERROR; + } + String msg = ex.getMessage(); + // remove log numbers. regEx => match 8 or more integers + msg = (msg != null) ? msg.replaceAll("\\d{8,}", "").trim() : "cmm.rest_api.model.import_failure"; + + throw new WebScriptException(statusCode, msg); + } + + return model; + } + + protected String getExtensionModule(InputStream inputStream, String fileName) + { + Element rootElement = null; + try + { + final DocumentBuilder db = XMLUtil.getDocumentBuilder(); + rootElement = db.parse(inputStream).getDocumentElement(); + } + catch (IOException io) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "cmm.rest_api.model.import_process_ext_module_file_failure", io); + } + catch (SAXException ex) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "cmm.rest_api.model.import_invalid_ext_module_entry", new Object[] { fileName }, ex); + } + + if (rootElement != null && SHARE_EXT_MODULE_ROOT_ELEMENT.equals(rootElement.getNodeName())) + { + StringWriter sw = new StringWriter(); + XMLUtil.print(rootElement, sw, false); + + return sw.toString(); + } + + return null; + } + + /** + * Simple POJO for model import result. + * + * @author Jamal Kaabi-Mofrad + */ + public static class ImportResult + { + private String importedModelName; + private String shareExtXMLFragment; + + public ImportResult(CustomModel customModel, String shareExtXMLFragment) + { + this.shareExtXMLFragment = shareExtXMLFragment; + if (customModel != null) + { + this.importedModelName = customModel.getName(); + } + } + + public String getImportedModelName() + { + return this.importedModelName; + } + + public String getShareExtXMLFragment() + { + return this.shareExtXMLFragment; + } + } +} diff --git a/source/java/org/alfresco/rest/api/CustomModels.java b/source/java/org/alfresco/rest/api/CustomModels.java new file mode 100644 index 0000000000..84c641bf01 --- /dev/null +++ b/source/java/org/alfresco/rest/api/CustomModels.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api; + +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModelConstraint; +import org.alfresco.rest.api.model.CustomModelDownload; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; + +/** + * @author Jamal Kaabi-Mofrad + */ +public interface CustomModels +{ + /** + * Gets the {@code org.alfresco.rest.api.model.CustomModel} representation for the given model + * + * @param modelName the model name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomModel} object + */ + public CustomModel getCustomModel(String modelName, Parameters parameters); + + /** + * Gets a paged list of all custom models + * + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return a paged list of {@code org.alfresco.rest.api.model.CustomModel} objects + */ + public CollectionWithPagingInfo getCustomModels(Parameters parameters); + + /** + * Creates custom model + * + * @param model the custom model to create + * @return {@code org.alfresco.rest.api.model.CustomModel} object + */ + public CustomModel createCustomModel(CustomModel model); + + /** + * Creates custom model from the imported {@link M2Model}. + * + * @param m2Model the model + * @return {@code org.alfresco.rest.api.model.CustomModel} object + */ + public CustomModel createCustomModel(M2Model m2Model); + + /** + * Updates or activates/deactivates the custom model + * + * @param modelName the model name + * @param model the custom model to update (JSON payload) + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomModel} object + */ + public CustomModel updateCustomModel(String modelName, CustomModel model, Parameters parameters); + + /** + * Deletes the custom model + * + * @param modelName the model name + */ + public void deleteCustomModel(String modelName); + + /** + * Gets the {@code org.alfresco.rest.api.model.CustomType} representation of + * the given model's type + * + * @param modelName the model name + * @param typeName the model's type name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomType} object + */ + public CustomType getCustomType(String modelName, String typeName, Parameters parameters); + + /** + * Gets a paged list of all the given custom model's types + * + * @param modelName the model name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return a paged list of {@code org.alfresco.rest.api.model.CustomType} objects + */ + public CollectionWithPagingInfo getCustomTypes(String modelName, Parameters parameters); + + /** + * Creates custom model's type + * + * @param modelName the model name + * @param type the custom type to create within the given model + * @return {@code org.alfresco.rest.api.model.CustomType} object + */ + public CustomType createCustomType(String modelName, CustomType type); + + /** + * Updates the custom model's type + * + * @param modelName the model name + * @param type the custom model's type to update (JSON payload) + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomType} object + */ + public CustomType updateCustomType(String modelName, CustomType type, Parameters parameters); + + /** + * Deletes the custom model's type + * + * @param modelName the model name + * @param typeName the model's type name + */ + public void deleteCustomType(String modelName, String typeName); + + /** + * Gets the {@code org.alfresco.rest.api.model.CustomAspect} representation of + * the given model's aspect + * + * @param modelName the model name + * @param aspectName the model's aspect name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomAspect} object + */ + public CustomAspect getCustomAspect(String modelName, String aspectName, Parameters parameters); + + /** + * Gets a paged list of all the given custom model's aspects + * + * @param modelName the model name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return a paged list of {@code org.alfresco.rest.api.model.CustomAspect} objects + */ + public CollectionWithPagingInfo getCustomAspects(String modelName, Parameters parameters); + + /** + * Creates custom model's aspect + * + * @param modelName the model name + * @param aspect the custom aspect to create within the given model + * @return {@code org.alfresco.rest.api.model.CustomAspect} object + */ + public CustomAspect createCustomAspect(String modelName, CustomAspect aspect); + + /** + * Updates the custom model's aspect + * + * @param modelName the model name + * @param aspect the custom model's aspect to update (JSON payload) + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomAspect} object + */ + public CustomAspect updateCustomAspect(String modelName, CustomAspect aspect, Parameters parameters); + + /** + * Deletes the custom model's aspect + * + * @param modelName the model name + * @param aspectName the model's aspect name + */ + public void deleteCustomAspect(String modelName, String aspectName); + + /** + * Gets the {@code org.alfresco.rest.api.model.CustomModelConstraint} + * representation of the given model's constraint + * + * @param modelName the model name + * @param constraintName the model's constraint name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return {@code org.alfresco.rest.api.model.CustomModelConstraint} object + */ + public CustomModelConstraint getCustomModelConstraint(String modelName, String constraintName, Parameters parameters); + + /** + * Gets a paged list of all of the given custom model's constraints + * + * @param modelName the model name + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return a paged list of {@code org.alfresco.rest.api.model.CustomModelConstraint} objects + */ + public CollectionWithPagingInfo getCustomModelConstraints(String modelName, Parameters parameters); + + /** + * Creates custom model's constraint + * + * @param modelName the model name + * @param constraint the custom constraint to create within the given model + * @return {@code org.alfresco.rest.api.model.CustomModelConstraint} object + */ + public CustomModelConstraint createCustomModelConstraint(String modelName, CustomModelConstraint constraint); + + /** + * Starts the creation of a downloadable archive file containing the + * custom model file and its associated Share extension module file (if requested). + * + * @param modelName the model name + * @param parameters the {@link Parameters} object to get the parameters + * passed into the request + * @return {@code org.alfresco.rest.api.model.CustomModelDownload} object + * containing the archive node reference + */ + public CustomModelDownload createDownload(String modelName, Parameters parameters); +} diff --git a/source/java/org/alfresco/rest/api/cmm/CustomModelAspectsRelation.java b/source/java/org/alfresco/rest/api/cmm/CustomModelAspectsRelation.java new file mode 100644 index 0000000000..0ee1a8094e --- /dev/null +++ b/source/java/org/alfresco/rest/api/cmm/CustomModelAspectsRelation.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.cmm; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Jamal Kaabi-Mofrad + */ +@RelationshipResource(name = "aspects", entityResource = CustomModelEntityResource.class, title = "Custom Model Aspects") +public class CustomModelAspectsRelation implements RelationshipResourceAction.Read, + RelationshipResourceAction.ReadById, + RelationshipResourceAction.Create, + RelationshipResourceAction.Update, + RelationshipResourceAction.Delete, + InitializingBean +{ + + private CustomModels customModels; + + public void setCustomModels(CustomModels customModels) + { + this.customModels = customModels; + } + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "customModels", customModels); + } + + @Override + @WebApiDescription(title = "Returns a paged list of all the custom model's aspects.") + public CollectionWithPagingInfo readAll(String modelName, Parameters parameters) + { + return customModels.getCustomAspects(modelName, parameters); + } + + @Override + @WebApiDescription(title = "Returns custom aspect information for the given 'aspectName' in 'modelName'.") + public CustomAspect readById(String modelName, String aspectName, Parameters parameters) + { + return customModels.getCustomAspect(modelName, aspectName, parameters); + } + + @Override + @WebApiDescription(title = "Removes the custom aspect for the given 'aspectName' in 'modelName'.") + public void delete(String modelName, String aspectName, Parameters parameters) + { + customModels.deleteCustomAspect(modelName, aspectName); + } + + @Override + @WebApiDescription(title = "Updates the custom aspect in the given 'modelName'.") + public CustomAspect update(String modelName, CustomAspect aspect, Parameters parameters) + { + return customModels.updateCustomAspect(modelName, aspect, parameters); + } + + @Override + @WebApiDescription(title = "Creates custom aspects for the model 'modelName'.") + public List create(String modelName, List aspects, Parameters parameters) + { + List result = new ArrayList<>(aspects.size()); + for (CustomAspect aspect : aspects) + { + result.add(customModels.createCustomAspect(modelName, aspect)); + } + return result; + } +} diff --git a/source/java/org/alfresco/rest/api/cmm/CustomModelConstraintRelation.java b/source/java/org/alfresco/rest/api/cmm/CustomModelConstraintRelation.java new file mode 100644 index 0000000000..5b6b3da2af --- /dev/null +++ b/source/java/org/alfresco/rest/api/cmm/CustomModelConstraintRelation.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.cmm; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.CustomModelConstraint; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Jamal Kaabi-Mofrad + */ +@RelationshipResource(name = "constraints", entityResource = CustomModelEntityResource.class, title = "Custom Model Constraints") +public class CustomModelConstraintRelation implements RelationshipResourceAction.Read, + RelationshipResourceAction.ReadById, + RelationshipResourceAction.Create, + InitializingBean +{ + + private CustomModels customModels; + + public void setCustomModels(CustomModels customModels) + { + this.customModels = customModels; + } + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "customModels", customModels); + } + + @Override + @WebApiDescription(title = "Returns a paged list of all the custom model's constraints.") + public CollectionWithPagingInfo readAll(String modelName, Parameters parameters) + { + return customModels.getCustomModelConstraints(modelName, parameters); + } + + @Override + @WebApiDescription(title = "Returns custom constraint information for the given 'constraintName' in 'modelName'.") + public CustomModelConstraint readById(String modelName, String constraintName, Parameters parameters) + { + return customModels.getCustomModelConstraint(modelName, constraintName, parameters); + } + + @Override + @WebApiDescription(title = "Creates custom constraints for the model 'modelName'.") + public List create(String modelName, List constraints, Parameters parameters) + { + List result = new ArrayList<>(constraints.size()); + for (CustomModelConstraint constraint : constraints) + { + result.add(customModels.createCustomModelConstraint(modelName, constraint)); + } + return result; + } +} diff --git a/source/java/org/alfresco/rest/api/cmm/CustomModelDownloadRelation.java b/source/java/org/alfresco/rest/api/cmm/CustomModelDownloadRelation.java new file mode 100644 index 0000000000..b546250f41 --- /dev/null +++ b/source/java/org/alfresco/rest/api/cmm/CustomModelDownloadRelation.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.cmm; + +import java.util.Collections; +import java.util.List; + +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.CustomModelDownload; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Jamal Kaabi-Mofrad + */ +@RelationshipResource(name = "download", entityResource = CustomModelEntityResource.class, title = "Custom Model Download") +public class CustomModelDownloadRelation implements RelationshipResourceAction.Create, InitializingBean +{ + + private CustomModels customModels; + + public void setCustomModels(CustomModels customModels) + { + this.customModels = customModels; + } + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "customModels", customModels); + } + + @Override + @WebApiDescription(title = "Creates download node containing the custom model file and if specified, its associated Share extension module file.") + public List create(String modelName, List download, Parameters parameters) + { + CustomModelDownload result = customModels.createDownload(modelName, parameters); + return Collections.singletonList(result); + } +} diff --git a/source/java/org/alfresco/rest/api/cmm/CustomModelEntityResource.java b/source/java/org/alfresco/rest/api/cmm/CustomModelEntityResource.java new file mode 100644 index 0000000000..0bd0da59e9 --- /dev/null +++ b/source/java/org/alfresco/rest/api/cmm/CustomModelEntityResource.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.cmm; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.resource.EntityResource; +import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Jamal Kaabi-Mofrad + */ +@EntityResource(name = "cmm", title = "Custom Model Management") +public class CustomModelEntityResource implements EntityResourceAction.Read, + EntityResourceAction.ReadById, + EntityResourceAction.Create, + EntityResourceAction.Update, + EntityResourceAction.Delete, + InitializingBean +{ + + private CustomModels customModels; + + public void setCustomModels(CustomModels customModels) + { + this.customModels = customModels; + } + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "customModels", customModels); + } + + @Override + @WebApiDescription(title="Returns custom model information for the given model name.") + public CustomModel readById(String modelName, Parameters parameters) throws EntityNotFoundException + { + return customModels.getCustomModel(modelName, parameters); + } + + @Override + @WebApiDescription(title="Returns a paged list of all custom models.") + public CollectionWithPagingInfo readAll(Parameters parameters) + { + return customModels.getCustomModels(parameters); + } + + @Override + @WebApiDescription(title="Creates custom model(s).") + public List create(List entity, Parameters parameters) + { + List result = new ArrayList<>(entity.size()); + for (CustomModel cm : entity) + { + result.add(customModels.createCustomModel(cm)); + } + return result; + } + + @Override + @WebApiDescription(title = "Updates or activates/deactivates the custom model.") + public CustomModel update(String modelName, CustomModel entity, Parameters parameters) + { + return customModels.updateCustomModel(modelName, entity, parameters); + } + + @Override + @WebApiDescription(title = "Deletes the custom model.") + public void delete(String modelName, Parameters parameters) + { + customModels.deleteCustomModel(modelName); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/cmm/CustomModelTypesRelation.java b/source/java/org/alfresco/rest/api/cmm/CustomModelTypesRelation.java new file mode 100644 index 0000000000..dab3bed39a --- /dev/null +++ b/source/java/org/alfresco/rest/api/cmm/CustomModelTypesRelation.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.cmm; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Jamal Kaabi-Mofrad + */ +@RelationshipResource(name = "types", entityResource = CustomModelEntityResource.class, title = "Custom Model Types") +public class CustomModelTypesRelation implements RelationshipResourceAction.Read, + RelationshipResourceAction.ReadById, + RelationshipResourceAction.Create, + RelationshipResourceAction.Update, + RelationshipResourceAction.Delete, + InitializingBean +{ + + private CustomModels customModels; + + public void setCustomModels(CustomModels customModels) + { + this.customModels = customModels; + } + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "customModels", customModels); + } + + @Override + @WebApiDescription(title = "Returns a paged list of all the custom model's types.") + public CollectionWithPagingInfo readAll(String modelName, Parameters parameters) + { + return customModels.getCustomTypes(modelName, parameters); + } + + @Override + @WebApiDescription(title = "Returns custom type information for the given 'typeName' in 'modelName'.") + public CustomType readById(String modelName, String typeName, Parameters parameters) + { + return customModels.getCustomType(modelName, typeName, parameters); + } + + @Override + @WebApiDescription(title = "Removes the custom type for the given 'typeName' in 'modelName'.") + public void delete(String modelName, String typeName, Parameters parameters) + { + customModels.deleteCustomType(modelName, typeName); + } + + @Override + @WebApiDescription(title = "Updates the custom type in the given 'modelName'.") + public CustomType update(String modelName, CustomType type, Parameters parameters) + { + return customModels.updateCustomType(modelName, type, parameters); + } + + @Override + @WebApiDescription(title = "Creates custom types for the model 'modelName'.") + public List create(String modelName, List types, Parameters parameters) + { + List result = new ArrayList<>(types.size()); + for (CustomType type : types) + { + result.add(customModels.createCustomType(modelName, type)); + } + return result; + } +} diff --git a/source/java/org/alfresco/rest/api/cmm/package-info.java b/source/java/org/alfresco/rest/api/cmm/package-info.java new file mode 100644 index 0000000000..728a8bc8cb --- /dev/null +++ b/source/java/org/alfresco/rest/api/cmm/package-info.java @@ -0,0 +1,4 @@ +@WebApi(name="alfresco", scope=Api.SCOPE.PRIVATE, version=1) +package org.alfresco.rest.api.cmm; +import org.alfresco.rest.framework.Api; +import org.alfresco.rest.framework.WebApi; diff --git a/source/java/org/alfresco/rest/api/impl/CustomModelsImpl.java b/source/java/org/alfresco/rest/api/impl/CustomModelsImpl.java new file mode 100644 index 0000000000..4579cf82b9 --- /dev/null +++ b/source/java/org/alfresco/rest/api/impl/CustomModelsImpl.java @@ -0,0 +1,1718 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.dictionary.CompiledModel; +import org.alfresco.repo.dictionary.CustomModelDefinitionImpl; +import org.alfresco.repo.dictionary.Facetable; +import org.alfresco.repo.dictionary.M2Aspect; +import org.alfresco.repo.dictionary.M2Class; +import org.alfresco.repo.dictionary.M2Constraint; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Namespace; +import org.alfresco.repo.dictionary.M2Property; +import org.alfresco.repo.dictionary.M2Type; +import org.alfresco.repo.dictionary.ValueDataTypeValidator; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.rest.api.CustomModels; +import org.alfresco.rest.api.model.AbstractClassModel; +import org.alfresco.rest.api.model.AbstractCommonDetails; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.model.CustomModelConstraint; +import org.alfresco.rest.api.model.CustomModelDownload; +import org.alfresco.rest.api.model.CustomModelNamedValue; +import org.alfresco.rest.api.model.CustomModelProperty; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.framework.core.exceptions.ApiException; +import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Paging; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.CustomModelDefinition; +import org.alfresco.service.cmr.dictionary.CustomModelException; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.ModelDefinition; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.dictionary.CustomModelException.ActiveModelConstraintException; +import org.alfresco.service.cmr.dictionary.CustomModelException.CustomModelConstraintException; +import org.alfresco.service.cmr.dictionary.CustomModelException.InvalidCustomModelException; +import org.alfresco.service.cmr.dictionary.CustomModelException.ModelDoesNotExistException; +import org.alfresco.service.cmr.dictionary.CustomModelException.ModelExistsException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.collections.CollectionUtils; +import org.alfresco.util.collections.Function; +import org.apache.commons.lang.StringUtils; +import org.springframework.extensions.surf.util.I18NUtil; + + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelsImpl implements CustomModels +{ + // for consistency the patterns are equivalent to the patterns defined in the cmm-misc.lib.js + public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_\\-]+$"); + public static final Pattern URI_PATTERN = Pattern.compile("^[A-Za-z0-9:/_\\.\\-]+$"); + + public static final String MODEL_NAME_NULL_ERR = "cmm.rest_api.model_name_null"; + public static final String TYPE_NAME_NULL_ERR = "cmm.rest_api.type_name_null"; + public static final String ASPECT_NAME_NULL_ERR = "cmm.rest_api.aspect_name_null"; + public static final String CONSTRAINT_NAME_NULL_ERR = "cmm.rest_api.constraint_name_null"; + + // Services + protected CustomModelService customModelService; + protected DictionaryService dictionaryService; + protected PersonService personService; + protected NodeService nodeService; + protected NamespaceService namespaceService; + protected ValueDataTypeValidator valueDataTypeValidator; + + private static final String DEFAULT_DATA_TYPE = "d:text"; + private static final String SELECT_ALL = "all"; + private static final String SELECT_STATUS = "status"; + private static final String SELECT_PROPS = "props"; + private static final String SELECT_ALL_PROPS = "allProps"; + private static final String PARAM_UPDATE_PROP = "update"; + private static final String PARAM_DELETE_PROP = "delete"; + private static final String PARAM_WITH_EXT_MODULE = "extModule"; + + public void setCustomModelService(CustomModelService customModelService) + { + this.customModelService = customModelService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setValueDataTypeValidator(ValueDataTypeValidator valueDataTypeValidator) + { + this.valueDataTypeValidator = valueDataTypeValidator; + } + + @Override + public CustomModel getCustomModel(String modelName, Parameters parameters) + { + CustomModelDefinition modelDef = getCustomModelImpl(modelName); + + if (hasSelectProperty(parameters, SELECT_ALL)) + { + return new CustomModel(modelDef, + convertToCustomTypes(modelDef.getTypeDefinitions(), false), + convertToCustomAspects(modelDef.getAspectDefinitions(), false), + convertToCustomModelConstraints(modelDef.getModelDefinedConstraints())); + } + + return new CustomModel(modelDef); + } + + private CustomModelDefinition getCustomModelImpl(String modelName) + { + if(modelName == null) + { + throw new InvalidArgumentException(MODEL_NAME_NULL_ERR); + } + + CustomModelDefinition model = null; + try + { + model = customModelService.getCustomModel(modelName); + } + catch (CustomModelException ex) + { + throw new EntityNotFoundException(modelName); + } + + if (model == null) + { + throw new EntityNotFoundException(modelName); + } + + return model; + } + + @Override + public CollectionWithPagingInfo getCustomModels(Parameters parameters) + { + Paging paging = parameters.getPaging(); + PagingRequest pagingRequest = Util.getPagingRequest(paging); + PagingResults results = customModelService.getCustomModels(pagingRequest); + + Integer totalItems = results.getTotalResultCount().getFirst(); + List page = results.getPage(); + + List models = new ArrayList<>(page.size()); + for (CustomModelDefinition modelDefinition : page) + { + models.add(new CustomModel(modelDefinition)); + } + + return CollectionWithPagingInfo.asPaged(paging, models, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue())); + } + + @Override + public CustomModel createCustomModel(CustomModel model) + { + // Check the current user is authorised to create a custom model + validateCurrentUser(); + return createCustomModelImpl(model, true); + } + + private CustomModel createCustomModelImpl(CustomModel model, boolean basicModelOnly) + { + M2Model m2Model = null; + if (basicModelOnly) + { + m2Model = convertToM2Model(model, null, null, null); + } + else + { + m2Model = convertToM2Model(model, model.getTypes(), model.getAspects(), model.getConstraints()); + } + + boolean activate = ModelStatus.ACTIVE.equals(model.getStatus()); + try + { + CustomModelDefinition modelDefinition = customModelService.createCustomModel(m2Model, activate); + return new CustomModel(modelDefinition); + } + catch (ModelExistsException me) + { + throw new ConstraintViolatedException(me.getMessage()); + } + catch (CustomModelConstraintException ncx) + { + throw new ConstraintViolatedException(ncx.getMessage()); + } + catch (InvalidCustomModelException iex) + { + throw new InvalidArgumentException(iex.getMessage()); + } + catch (Exception e) + { + throw new ApiException("cmm.rest_api.model_invalid", e); + } + } + + @Override + public CustomModel updateCustomModel(String modelName, CustomModel model, Parameters parameters) + { + // Check the current user is authorised to update the custom model + validateCurrentUser(); + + // Check to see if the model exists + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + CustomModel existingModel = existingModelDetails.getModel(); + + // The model just needs to be activated/deactivated (in other words, + // the other properties should be untouched) + if (hasSelectProperty(parameters, SELECT_STATUS)) + { + ModelStatus status = model.getStatus(); + if (status == null) + { + throw new InvalidArgumentException("cmm.rest_api.model_status_null"); + } + try + { + if (ModelStatus.ACTIVE.equals(status)) + { + customModelService.activateCustomModel(modelName); + } + else + { + customModelService.deactivateCustomModel(modelName); + } + // update the model's status + existingModel.setStatus(status); + return existingModel; + } + catch (CustomModelConstraintException mce) + { + throw new ConstraintViolatedException(mce.getMessage()); + } + catch (Exception ex) + { + throw new ApiException(ex.getMessage(), ex); + } + } + else + { + if (model.getName() != null && !(existingModel.getName().equals(model.getName()))) + { + throw new InvalidArgumentException("cmm.rest_api.model_name_cannot_update"); + } + + existingModel.setNamespaceUri(model.getNamespaceUri()); + final boolean isNamespacePrefixChanged = !(existingModel.getNamespacePrefix().equals(model.getNamespacePrefix())); + if(isNamespacePrefixChanged) + { + // Change types' and aspects' parents as well as the property constraint's Ref namespace prefix + replacePrefix(existingModelDetails.getTypes(), existingModel.getNamespacePrefix(), model.getNamespacePrefix()); + replacePrefix(existingModelDetails.getAspects(), existingModel.getNamespacePrefix(), model.getNamespacePrefix()); + } + existingModel.setNamespacePrefix(model.getNamespacePrefix()); + existingModel.setAuthor(model.getAuthor()); + existingModel.setDescription(model.getDescription()); + + CustomModelDefinition modelDef = updateModel(existingModelDetails, "cmm.rest_api.model_update_failure"); + return new CustomModel(modelDef); + } + } + + private void replacePrefix(List existingTypesOrAspects, String modelOldNamespacePrefix, String modelNewNamespacePrefix) + { + for(AbstractClassModel classModel : existingTypesOrAspects) + { + // Type/Aspect's parent name + String parentName = classModel.getParentName(); + if(parentName != null) + { + Pair prefixLocalNamePair = splitPrefixedQName(parentName); + // Check to see if the parent name prefix, is the namespace prefix of the model being edited. + // As we don't want to modify the parent name of the imported models. + if(modelOldNamespacePrefix.equals(prefixLocalNamePair.getFirst())) + { + // Change the parent name prefix, to a new model namespace prefix. + String newParentName = constructName(prefixLocalNamePair.getSecond(), modelNewNamespacePrefix); + classModel.setParentName(newParentName); + } + } + + // Change the property constraint ref + List properties = classModel.getProperties(); + for(CustomModelProperty prop : properties) + { + List constraintRefs = prop.getConstraintRefs(); + if(constraintRefs.size() > 0) + { + List modifiedRefs = new ArrayList<>(constraintRefs.size()); + for(String ref : constraintRefs) + { + // We don't need to check if the prefix is equal to the model prefix here, as it was + // done upon adding the constraint refs in the setM2Properties method. + Pair prefixLocalNamePair = splitPrefixedQName(ref); + // Change the constraint ref prefix, to a new model namespace prefix. + String newRef = constructName(prefixLocalNamePair.getSecond(), modelNewNamespacePrefix); + modifiedRefs.add(newRef); + } + prop.setConstraintRefs(modifiedRefs); + } + } + } + } + + @Override + public void deleteCustomModel(String modelName) + { + // Check the current user is authorised to delete the custom model + validateCurrentUser(); + + if(modelName == null) + { + throw new InvalidArgumentException(MODEL_NAME_NULL_ERR); + } + + try + { + customModelService.deleteCustomModel(modelName); + } + catch (ModelDoesNotExistException ee) + { + throw new EntityNotFoundException(modelName); + } + catch (ActiveModelConstraintException ae) + { + throw new ConstraintViolatedException(ae.getMessage()); + } + catch (Exception ex) + { + throw new ApiException(ex.getMessage(), ex); + } + } + + @Override + public CustomType getCustomType(String modelName, String typeName, Parameters parameters) + { + if(typeName == null) + { + throw new InvalidArgumentException(TYPE_NAME_NULL_ERR); + } + + final CustomModelDefinition modelDef = getCustomModelImpl(modelName); + QName typeQname = QName.createQName(modelDef.getName().getNamespaceURI(), typeName); + + TypeDefinition customTypeDef = customModelService.getCustomType(typeQname); + if (customTypeDef == null) + { + throw new EntityNotFoundException(typeName); + } + + // Check if inherited properties have been requested + boolean includeInheritedProps = hasSelectProperty(parameters, SELECT_ALL_PROPS); + return convertToCustomType(customTypeDef, includeInheritedProps); + } + + @Override + public CollectionWithPagingInfo getCustomTypes(String modelName, Parameters parameters) + { + CustomModelDefinition modelDef = getCustomModelImpl(modelName); + Collection typeDefinitions = modelDef.getTypeDefinitions(); + // TODO Should we support paging? + Paging paging = Paging.DEFAULT; + + List customTypes = convertToCustomTypes(typeDefinitions, false); + + return CollectionWithPagingInfo.asPaged(paging, customTypes, false, typeDefinitions.size()); + + } + + @Override + public CustomType createCustomType(String modelName, CustomType type) + { + // Check the current user is authorised to update the custom model + validateCurrentUser(); + + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + + // Validate type's parent + validateTypeAspectParent(type, existingModelDetails.getModel()); + existingModelDetails.getTypes().add(type); + + updateModel(existingModelDetails, "cmm.rest_api.type_create_failure"); + return type; + } + + @Override + public CustomType updateCustomType(String modelName, CustomType type, Parameters parameters) + { + return updateTypeAspect(modelName, type, parameters); + } + + private T updateTypeAspect(String modelName, T classDef, Parameters parameters) + { + // Check the current user is authorised to update the custom model + validateCurrentUser(); + + final boolean isAspect = classDef instanceof CustomAspect; + + String name = classDef.getName(); + if(name == null) + { + String msgId = isAspect ? ASPECT_NAME_NULL_ERR : TYPE_NAME_NULL_ERR; + throw new InvalidArgumentException(msgId); + } + + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + + List allClassDefs = isAspect ? existingModelDetails.getAspects() : existingModelDetails.getTypes(); + + @SuppressWarnings("unchecked") + T existingClassDef = (T) getObjectByName(allClassDefs, name); + if (existingClassDef == null) + { + throw new EntityNotFoundException(name); + } + + if (hasSelectProperty(parameters, SELECT_PROPS)) + { + String errorMsg = null; + String propName = parameters.getParameter(PARAM_DELETE_PROP); + if (propName == null) + { + errorMsg = "cmm.rest_api.property_create_update_failure"; + // Add/Update properties + mergeProperties(existingClassDef, classDef, parameters, existingModelDetails.isActive()); + } + else //Delete property request + { + errorMsg = "cmm.rest_api.property_delete_failure"; + deleteProperty(existingClassDef, propName); + } + + updateModel(existingModelDetails, errorMsg); + } + else + { + existingClassDef.setTitle(classDef.getTitle()); + existingClassDef.setDescription(classDef.getDescription()); + final boolean isParentChanged = !(StringUtils.equals(existingClassDef.getParentName(), classDef.getParentName())); + if (isParentChanged && existingModelDetails.isActive()) + { + String errMsgId = isAspect ? "cmm.rest_api.aspect_parent_cannot_update" : "cmm.rest_api.type_parent_cannot_update"; + throw new ConstraintViolatedException(errMsgId); + } + // Validate type/aspect parent + validateTypeAspectParent(classDef, existingModelDetails.getModel()); + existingClassDef.setParentName(classDef.getParentName()); + + String errMsgId = isAspect ? "cmm.rest_api.aspect_update_failure" : "cmm.rest_api.type_update_failure"; + updateModel(existingModelDetails, errMsgId); + } + return existingClassDef; + } + + @Override + public void deleteCustomType(String modelName, String typeName) + { + // Check the current user is authorised to delete the custom model's type + validateCurrentUser(); + + if(typeName == null) + { + throw new InvalidArgumentException(TYPE_NAME_NULL_ERR); + } + + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + if(existingModelDetails.isActive()) + { + throw new ConstraintViolatedException("cmm.rest_api.type_cannot_delete"); + } + + Map allTypes = transformToMap(existingModelDetails.getTypes(), toNameFunction()); + CustomType typeToBeDeleted = allTypes.get(typeName); + + if(typeToBeDeleted == null) + { + throw new EntityNotFoundException(typeName); + } + + // Validate type's dependency + validateTypeAspectDelete(allTypes.values(), typeToBeDeleted.getPrefixedName()); + + // Remove the validated type + allTypes.remove(typeName); + existingModelDetails.setTypes(new ArrayList<>(allTypes.values())); + + updateModel(existingModelDetails, "cmm.rest_api.type_delete_failure"); + } + + @Override + public CustomAspect getCustomAspect(String modelName, String aspectName, Parameters parameters) + { + if(aspectName == null) + { + throw new InvalidArgumentException(ASPECT_NAME_NULL_ERR); + } + + final CustomModelDefinition modelDef = getCustomModelImpl(modelName); + QName aspectQname = QName.createQName(modelDef.getName().getNamespaceURI(), aspectName); + + AspectDefinition customAspectDef = customModelService.getCustomAspect(aspectQname); + if (customAspectDef == null) + { + throw new EntityNotFoundException(aspectName); + } + + // Check if inherited properties have been requested + boolean includeInheritedProps = hasSelectProperty(parameters, SELECT_ALL_PROPS); + return convertToCustomAspect(customAspectDef, includeInheritedProps); + } + + @Override + public CollectionWithPagingInfo getCustomAspects(String modelName, Parameters parameters) + { + CustomModelDefinition modelDef = getCustomModelImpl(modelName); + Collection aspectDefinitions = modelDef.getAspectDefinitions(); + // TODO Should we support paging? + Paging paging = Paging.DEFAULT; + + List customAspects = convertToCustomAspects(aspectDefinitions, false); + + return CollectionWithPagingInfo.asPaged(paging, customAspects, false, aspectDefinitions.size()); + } + + @Override + public CustomAspect createCustomAspect(String modelName, CustomAspect aspect) + { + // Check the current user is authorised to update the custom model + validateCurrentUser(); + + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + + // Validate aspect's parent + validateTypeAspectParent(aspect, existingModelDetails.getModel()); + existingModelDetails.getAspects().add(aspect); + + updateModel(existingModelDetails, "cmm.rest_api.aspect_create_failure"); + return aspect; + } + + @Override + public CustomAspect updateCustomAspect(String modelName, CustomAspect aspect, Parameters parameters) + { + return updateTypeAspect(modelName, aspect, parameters); + } + + @Override + public void deleteCustomAspect(String modelName, String aspectName) + { + // Check the current user is authorised to delete the custom model's aspect + validateCurrentUser(); + + if(aspectName == null) + { + throw new InvalidArgumentException(ASPECT_NAME_NULL_ERR); + } + + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + if(existingModelDetails.isActive()) + { + throw new ConstraintViolatedException("cmm.rest_api.aspect_cannot_delete"); + } + + Map allAspects = transformToMap(existingModelDetails.getAspects(), toNameFunction()); + CustomAspect aspectToBeDeleted = allAspects.get(aspectName); + + if(aspectToBeDeleted == null) + { + throw new EntityNotFoundException(aspectName); + } + + // Validate aspect's dependency + validateTypeAspectDelete(allAspects.values(), aspectToBeDeleted.getPrefixedName()); + + // Remove the validated aspect + allAspects.remove(aspectName); + existingModelDetails.setAspects(new ArrayList<>(allAspects.values())); + + updateModel(existingModelDetails, "cmm.rest_api.aspect_delete_failure"); + } + + @Override + public CollectionWithPagingInfo getCustomModelConstraints(String modelName, Parameters parameters) + { + CustomModelDefinition modelDef = getCustomModelImpl(modelName); + Collection constraintDefinitions = modelDef.getModelDefinedConstraints(); + // TODO Should we support paging? + Paging paging = Paging.DEFAULT; + + List customModelConstraints = convertToCustomModelConstraints(constraintDefinitions); + + return CollectionWithPagingInfo.asPaged(paging, customModelConstraints, false, constraintDefinitions.size()); + } + + @Override + public CustomModelConstraint getCustomModelConstraint(String modelName, String constraintName, Parameters parameters) + { + if (constraintName == null) + { + throw new InvalidArgumentException(CONSTRAINT_NAME_NULL_ERR); + } + + final CustomModelDefinition modelDef = getCustomModelImpl(modelName); + QName constraintQname = QName.createQName(modelDef.getName().getNamespaceURI(), constraintName); + + ConstraintDefinition constraintDef = customModelService.getCustomConstraint(constraintQname); + if (constraintDef == null) + { + throw new EntityNotFoundException(constraintName); + } + + return new CustomModelConstraint(constraintDef, dictionaryService); + } + + @Override + public CustomModelConstraint createCustomModelConstraint(String modelName, CustomModelConstraint constraint) + { + // Check the current user is authorised to create constraints + validateCurrentUser(); + + ModelDetails existingModelDetails = new ModelDetails(getCustomModelImpl(modelName)); + + existingModelDetails.getModelDefinedConstraints().add(constraint); + + updateModel(existingModelDetails, "cmm.rest_api.constraint_create_failure"); + return constraint; + } + + @Override + public CustomModelDownload createDownload(String modelName, Parameters parameters) + { + // Check the current user is authorised to export the model + validateCurrentUser(); + + if (modelName == null) + { + throw new InvalidArgumentException(MODEL_NAME_NULL_ERR); + } + + String propName = parameters.getParameter(PARAM_WITH_EXT_MODULE); + boolean withForm = Boolean.valueOf(propName); + try + { + NodeRef nodeRef = customModelService.createDownloadNode(modelName, withForm); + return new CustomModelDownload(nodeRef); + } + catch (Exception ex) + { + String errorMsg = "cmm.rest_api.model_download_failure"; + if (ex.getMessage() != null) + { + errorMsg = ex.getMessage(); + } + throw new ApiException(errorMsg, ex); + } + } + + private CustomType convertToCustomType(TypeDefinition typeDefinition, boolean includeInheritedProps) + { + List properties = convertToCustomModelProperty(typeDefinition, includeInheritedProps); + return new CustomType(typeDefinition, dictionaryService, properties); + } + + private List convertToCustomTypes(Collection typeDefinitions, boolean includeInheritedProps) + { + // Convert a collection of TypeDefinitions into a list of CustomTypes + List customTypes = new ArrayList<>(typeDefinitions.size()); + for (TypeDefinition td : typeDefinitions) + { + customTypes.add(convertToCustomType(td, includeInheritedProps)); + } + + return customTypes; + } + + private CustomAspect convertToCustomAspect(AspectDefinition aspectDefinition, boolean includeInheritedProps) + { + List properties = convertToCustomModelProperty(aspectDefinition, includeInheritedProps); + return new CustomAspect(aspectDefinition, dictionaryService, properties); + } + + private List convertToCustomAspects(Collection aspectDefinitions, boolean includeInheritedProps) + { + // Convert a collection of AspectDefinitions into a list of CustomAspect + List customAspects = new ArrayList<>(aspectDefinitions.size()); + for (AspectDefinition ad : aspectDefinitions) + { + customAspects.add(convertToCustomAspect(ad, includeInheritedProps)); + } + + return customAspects; + } + + private List convertToCustomModelProperty(ClassDefinition classDefinition, boolean includeInherited) + { + Collection ownProperties = null; + ClassDefinition parentDef = classDefinition.getParentClassDefinition(); + if (!includeInherited && parentDef != null) + { + // Remove inherited properties + ownProperties = removeRightEntries(classDefinition.getProperties(), parentDef.getProperties()).values(); + } + else + { + ownProperties = classDefinition.getProperties().values(); + } + + List customProperties = new ArrayList<>(ownProperties.size()); + for (PropertyDefinition propDef : ownProperties) + { + customProperties.add(new CustomModelProperty(propDef, dictionaryService)); + } + + return customProperties; + } + + private List convertToCustomModelConstraints(Collection constraintDefinitions) + { + List constraints = new ArrayList<>(constraintDefinitions.size()); + for (ConstraintDefinition definition : constraintDefinitions) + { + constraints.add(new CustomModelConstraint(definition, dictionaryService)); + } + return constraints; + } + + /** + * Converts the given {@code ModelDetails} object into a {@link M2Model} object + * + * @param modelDetails the custom model details + * @return {@link M2Model} object + */ + private M2Model convertToM2Model(ModelDetails modelDetails) + { + return convertToM2Model(modelDetails.getModel(), modelDetails.getTypes(), modelDetails.getAspects(), modelDetails.getModelDefinedConstraints()); + } + + /** + * Converts the given {@code org.alfresco.rest.api.model.CustomModel} + * object, a collection of {@code org.alfresco.rest.api.model.CustomType} + * objects, a collection of + * {@code org.alfresco.rest.api.model.CustomAspect} objects, and a collection of + * {@code org.alfresco.rest.api.model.CustomModelConstraint} objects into a {@link M2Model} object + * + * @param customModel the custom model + * @param types the custom types + * @param aspects the custom aspects + * @param constraints the custom constraints + * @return {@link M2Model} object + */ + private M2Model convertToM2Model(CustomModel customModel, Collection types, Collection aspects, Collection constraints) + { + validateBasicModelInput(customModel); + + Set> namespacesToImport = new LinkedHashSet<>(); + + final String namespacePrefix = customModel.getNamespacePrefix(); + final String namespaceURI = customModel.getNamespaceUri(); + // Construct the model name + final String name = constructName(customModel.getName(), namespacePrefix); + + M2Model model = M2Model.createModel(name); + model.createNamespace(namespaceURI, namespacePrefix); + model.setDescription(customModel.getDescription()); + String author = customModel.getAuthor(); + if (author == null) + { + author = getCurrentUserFullName(); + } + model.setAuthor(author); + + // Types + if(types != null) + { + for(CustomType type : types) + { + validateName(type.getName(), TYPE_NAME_NULL_ERR); + M2Type m2Type = model.createType(constructName(type.getName(), namespacePrefix)); + m2Type.setDescription(type.getDescription()); + m2Type.setTitle(type.getTitle()); + setParentName(m2Type, type.getParentName(), namespacesToImport, namespacePrefix); + setM2Properties(m2Type, type.getProperties(), namespacePrefix, namespacesToImport); + } + } + + // Aspects + if(aspects != null) + { + for(CustomAspect aspect : aspects) + { + validateName(aspect.getName(), ASPECT_NAME_NULL_ERR); + M2Aspect m2Aspect = model.createAspect(constructName(aspect.getName(), namespacePrefix)); + m2Aspect.setDescription(aspect.getDescription()); + m2Aspect.setTitle(aspect.getTitle()); + setParentName(m2Aspect, aspect.getParentName(), namespacesToImport, namespacePrefix); + setM2Properties(m2Aspect, aspect.getProperties(), namespacePrefix, namespacesToImport); + } + } + + // Constraints + if(constraints != null) + { + for (CustomModelConstraint constraint : constraints) + { + validateName(constraint.getName(), CONSTRAINT_NAME_NULL_ERR); + final String constraintName = constructName(constraint.getName(), namespacePrefix); + M2Constraint m2Constraint = model.createConstraint(constraintName, constraint.getType()); + // Set title, desc and parameters + setConstraintOtherData(constraint, m2Constraint, null); + } + } + + // Add imports + for (Pair uriPrefix : namespacesToImport) + { + // Don't import the already defined namespace + if (!namespaceURI.equals(uriPrefix.getFirst())) + { + model.createImport(uriPrefix.getFirst(), uriPrefix.getSecond()); + } + } + + return model; + } + + private void setConstraintOtherData(CustomModelConstraint constraint, M2Constraint m2Constraint, String propDataType) + { + if (m2Constraint.getType() == null) + { + throw new InvalidArgumentException("cmm.rest_api.constraint_type_null"); + } + + ConstraintValidator constraintValidator = ConstraintValidator.findByType(m2Constraint.getType()); + if (propDataType != null) + { + // Check if the constraint can be used with given data type + constraintValidator.validateUsage(prefixedStringToQname(propDataType)); + } + + m2Constraint.setTitle(constraint.getTitle()); + m2Constraint.setDescription(constraint.getDescription()); + for (CustomModelNamedValue parameter : constraint.getParameters()) + { + validateName(parameter.getName(), "cmm.rest_api.constraint_parameter_name_null"); + if (parameter.getListValue() != null) + { + if (propDataType != null && "allowedValues".equals(parameter.getName())) + { + validateListConstraint(parameter.getListValue(), propDataType); + } + m2Constraint.createParameter(parameter.getName(), parameter.getListValue()); + } + else + { + constraintValidator.validate(parameter.getName(), parameter.getSimpleValue()); + m2Constraint.createParameter(parameter.getName(), parameter.getSimpleValue()); + } + } + } + + /* + * List constraint is a special case, so can't use the ConstraintValidator. + */ + private void validateListConstraint(List listValue, String propDataType) + { + for (String value : listValue) + { + try + { + // validate list values + this.valueDataTypeValidator.validateValue(propDataType, value); + } + catch (Exception ex) + { + throw new InvalidArgumentException(ex.getMessage()); + } + } + } + + private void setM2Properties(M2Class m2Class, List properties, String namespacePrefix, + Set> namespacesToImport) + { + if (properties != null) + { + for (CustomModelProperty prop : properties) + { + validateName(prop.getName(), "cmm.rest_api.property_name_null"); + M2Property m2Property = m2Class.createProperty(constructName(prop.getName(), namespacePrefix)); + m2Property.setTitle(prop.getTitle()); + m2Property.setDescription(prop.getDescription()); + m2Property.setMandatory(prop.isMandatory()); + m2Property.setMandatoryEnforced(prop.isMandatoryEnforced()); + m2Property.setMultiValued(prop.isMultiValued()); + // Set indexing options + m2Property.setIndexed(prop.isIndexed()); + if (Facetable.TRUE == prop.getFacetable()) + { + m2Property.setFacetable(true); + } + else if (Facetable.FALSE == prop.getFacetable()) + { + m2Property.setFacetable(false); + } + m2Property.setIndexTokenisationMode(prop.getIndexTokenisationMode()); + + String dataType = prop.getDataType(); + // Default type is d:text + if (StringUtils.isBlank(dataType)) + { + dataType = DEFAULT_DATA_TYPE; + } + else + { + if (!dataType.contains(":")) + { + throw new InvalidArgumentException("cmm.rest_api.property_datatype_invalid", new Object[] { dataType }); + } + } + namespacesToImport.add(resolveToUriAndPrefix(dataType)); + try + { + // validate default values + this.valueDataTypeValidator.validateValue(dataType, prop.getDefaultValue()); + } + catch (Exception ex) + { + throw new InvalidArgumentException(ex.getMessage()); + } + m2Property.setType(dataType); + m2Property.setDefaultValue(prop.getDefaultValue()); + + // Check for constraints + List constraintRefs = prop.getConstraintRefs(); + List constraints = prop.getConstraints(); + if (constraintRefs.size() > 0) + { + for (String ref : constraintRefs) + { + Pair prefixLocalName = splitPrefixedQName(ref); + if (!namespacePrefix.equals(prefixLocalName.getFirst())) + { + throw new ConstraintViolatedException(I18NUtil.getMessage("cmm.rest_api.constraint_ref_not_defined", ref)); + } + m2Property.addConstraintRef(ref); + } + } + if(constraints.size() > 0) + { + for (CustomModelConstraint modelConstraint : constraints) + { + String constraintName = null; + if (modelConstraint.getName() != null) + { + validateName(modelConstraint.getName(), CONSTRAINT_NAME_NULL_ERR); + constraintName = constructName(modelConstraint.getName(), namespacePrefix); + } + M2Constraint m2Constraint = m2Property.addConstraint(constraintName, modelConstraint.getType()); + // Set title, desc and parameters + setConstraintOtherData(modelConstraint, m2Constraint, dataType); + } + } + } + } + } + + private void validateBasicModelInput(CustomModel customModel) + { + // validate model name + validateName(customModel.getName(), MODEL_NAME_NULL_ERR); + + // validate model namespace prefix + validateName(customModel.getNamespacePrefix(), "cmm.rest_api.model_namespace_prefix_null"); + + // validate model namespace URI + if (customModel.getNamespaceUri() == null) + { + throw new InvalidArgumentException("cmm.rest_api.model_namespace_uri_null"); + } + else + { + Matcher matcher = URI_PATTERN.matcher(customModel.getNamespaceUri()); + if (!matcher.find()) + { + throw new InvalidArgumentException("cmm.rest_api.model_namespace_uri_invalid"); + } + } + } + + private void validateName(String name, String errMsgId) + { + if (name == null) + { + if (errMsgId == null) + { + errMsgId = InvalidArgumentException.DEFAULT_MESSAGE_ID; + } + throw new InvalidArgumentException(errMsgId); + } + else + { + Matcher matcher = NAME_PATTERN.matcher(name); + if (!matcher.find()) + { + throw new InvalidArgumentException("cmm.rest_api.input_validation_err", new Object [] {name}); + } + } + } + + /** + * Checks the current user access rights and throws + * {@link PermissionDeniedException} if the user is not a member of the + * ALFRESCO_MODEL_ADMINISTRATORS group + */ + private void validateCurrentUser() + { + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + if (!customModelService.isModelAdmin(currentUser)) + { + throw new PermissionDeniedException(); + } + } + + /** + * Gets the fully authenticated user's full name + * + * @return user's full name or the user's id if the full name dose not exit + */ + protected String getCurrentUserFullName() + { + String userName = AuthenticationUtil.getFullyAuthenticatedUser(); + NodeRef personRef = personService.getPerson(userName, false); + + String firstName = (String) nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); + String lastName = (String) nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); + + String fullName = (firstName != null ? firstName + " " : "") + (lastName != null ? lastName : ""); + + return ((StringUtils.isBlank(fullName) ? userName : fullName)).trim(); + } + + private String constructName(String name, String prefix) + { + return new StringBuilder(100).append(prefix).append(QName.NAMESPACE_PREFIX).append(name).toString(); + } + + /** + * Gets the namespace URI and prefix from the parent's name, provided that the + * given name is of a valid format. The valid format consist of a + * namespace prefix, a colon and a name. E.g. sys:localized + * + * @param parentName the parent name + * @return a pair of namespace URI and prefix object + */ + protected Pair resolveToUriAndPrefix(String parentName) + { + QName qName = prefixedStringToQname(parentName); + Collection prefixes = namespaceService.getPrefixes(qName.getNamespaceURI()); + if (prefixes.size() == 0) + { + throw new InvalidArgumentException("cmm.rest_api.prefix_not_registered", new Object[] { qName.getNamespaceURI() }); + } + String prefix = prefixes.iterator().next(); + return new Pair(qName.getNamespaceURI(), prefix); + } + + /** + * Creates {@link QName} from a valid prefixed string. + */ + private QName prefixedStringToQname(String prefixedQName) + { + try + { + return QName.createQName(prefixedQName, namespaceService); + } + catch (Exception ex) + { + String msg = ex.getMessage(); + if (msg == null) + { + msg = ""; + } + throw new InvalidArgumentException("cmm.rest_api.prefixed_qname_invalid", new Object[] { prefixedQName, msg }); + } + } + + /** + * Validates and sets the type's or aspect's parent name + * + * @param m2Class the {@link M2Type} or {@link M2Aspect} object + * @param parentPrefixedName the parent prefixed name. E.g. prefix:localName + * @param namespacesToImport the {@link Set} of namespace pairs to import + * @param modelNamespacePrefix the model namespace prefix + */ + private void setParentName(M2Class m2Class, String parentPrefixedName, Set> namespacesToImport, String modelNamespacePrefix) + { + if (StringUtils.isBlank(parentPrefixedName)) + { + return; + } + + Pair prefixLocaNamePair = splitPrefixedQName(parentPrefixedName); + if (!modelNamespacePrefix.equals(prefixLocaNamePair.getFirst())) + { + // Add to the list of imports + Pair uriPrefixPair = resolveToUriAndPrefix(parentPrefixedName); + namespacesToImport.add(uriPrefixPair); + } + m2Class.setParentName(parentPrefixedName); + } + + private void validateTypeAspectParent(AbstractClassModel typeAspect, CustomModel existingModel) + { + String parentPrefixedName = typeAspect.getParentName(); + if (StringUtils.isBlank(parentPrefixedName)) + { + return; + } + + Pair prefixLocaNamePair = splitPrefixedQName(parentPrefixedName); + String parentPrefix = prefixLocaNamePair.getFirst(); + String parentLocalName = prefixLocaNamePair.getSecond(); + + // Validate parent prefix and localName + // We know that the values are not null, we just check against the defined RegEx + validateName(parentPrefix, null); + validateName(parentLocalName, null); + + final boolean isAspect = (typeAspect instanceof CustomAspect); + ClassDefinition classDefinition = null; + QName qname = null; + if (existingModel.getNamespacePrefix().equals(parentPrefix)) + { + // Check for types/aspects within the model + qname = QName.createQName(existingModel.getNamespaceUri(), parentLocalName); + classDefinition = (isAspect) ? customModelService.getCustomAspect(qname) : customModelService.getCustomType(qname); + } + else + { + // Make sure the namespace URI and Prefix are registered + Pair uriPrefixPair = resolveToUriAndPrefix(parentPrefixedName); + + qname = QName.createQName(uriPrefixPair.getFirst(), parentLocalName); + classDefinition = (isAspect) ? dictionaryService.getAspect(qname) : dictionaryService.getType(qname); + } + + if (classDefinition == null) + { + String msgId = (isAspect) ? "cmm.rest_api.aspect_parent_not_exist" : "cmm.rest_api.type_parent_not_exist"; + throw new ConstraintViolatedException(I18NUtil.getMessage(msgId, parentPrefixedName)); + } + else + { + checkCircularDependency(classDefinition.getModel(), existingModel, parentPrefixedName); + } + } + + /** + * Validates models circular dependencies + *

E.g. if {@literal B -> A} denotes model B depends on model A, then {@link ConstraintViolatedException} must be thrown for following: + *

  • if {@literal B -> A}, then {@literal A -> B} must throw exception
  • + *
  • if {@literal B -> A} and {@literal C -> B}, then {@literal A -> C} must throw exception
  • + *
  • if {@literal B -> A} and {@literal C -> B} and {@literal D -> C}, then {@literal A -> D} must throw exception
  • + * @param modelDefinition the model which has a reference to the model containing the {@code parentPrefixedName} + * @param existingModel the model being updated + * @param parentPrefixedName the type/aspect parent name + */ + private void checkCircularDependency(ModelDefinition modelDefinition, CustomModel existingModel, String parentPrefixedName) + { + for (NamespaceDefinition importedNamespace : modelDefinition.getImportedNamespaces()) + { + ModelDefinition md = null; + if ((md = customModelService.getCustomModelByUri(importedNamespace.getUri())) != null) + { + if (existingModel.getNamespaceUri().equals(importedNamespace.getUri())) + { + String msg = I18NUtil.getMessage("cmm.rest_api.circular_dependency_err", parentPrefixedName, existingModel.getName()); + throw new ConstraintViolatedException(msg); + } + checkCircularDependency(md, existingModel, parentPrefixedName); + } + } + } + + /** + * Returns the qualified name of the following format + * prefix:localName, as a pair of (prefix, localName) + * + * @param prefixedQName the prefixed name. E.g. prefix:localName + * @return {@link Pair} of (prefix, localName) + */ + private Pair splitPrefixedQName(String prefixedQName) + { + // index 0 => prefix and index 1 => local name + String[] prefixLocalName = QName.splitPrefixedQName(prefixedQName); + + if (NamespaceService.DEFAULT_PREFIX.equals(prefixLocalName[0])) + { + throw new InvalidArgumentException("cmm.rest_api.prefixed_qname_invalid_format", new Object[] { prefixedQName }); + } + + return new Pair(prefixLocalName[0], prefixLocalName[1]); + } + + private CustomModelDefinition updateModel(ModelDetails modelDetails, String errorMsg) + { + M2Model m2Model = convertToM2Model(modelDetails); + try + { + CustomModelDefinition modelDef = customModelService.updateCustomModel(modelDetails.getModel().getName(), m2Model, modelDetails.isActive()); + return modelDef; + } + catch (CustomModelConstraintException mce) + { + throw new ConstraintViolatedException(mce.getMessage()); + } + catch (InvalidCustomModelException iex) + { + throw new InvalidArgumentException(iex.getMessage()); + } + catch (Exception ex) + { + if (ex.getMessage() != null) + { + errorMsg = ex.getMessage(); + } + throw new ApiException(errorMsg, ex); + } + } + + private void mergeProperties(AbstractClassModel existingDetails, AbstractClassModel newDetails, Parameters parameters, boolean isModelActive) + { + validateList(newDetails.getProperties(), "cmm.rest_api.properties_empty_null"); + + // Transform existing properties into a map + Map existingProperties = transformToMap(existingDetails.getProperties(), toNameFunction()); + + // Transform new properties into a map + Map newProperties = transformToMap(newDetails.getProperties(), toNameFunction()); + + String propName = parameters.getParameter(PARAM_UPDATE_PROP); + // (propName == null) => property create request + if (propName == null) + { + // As this is a create request, check for duplicate properties + for (String name : newProperties.keySet()) + { + if (existingProperties.containsKey(name)) + { + throw new ConstraintViolatedException(I18NUtil.getMessage("cmm.rest_api.property_create_name_already_in_use", name)); + } + } + } + else + {// Update request + CustomModelProperty existingProp = existingProperties.get(propName); + if (existingProp == null) + { + throw new EntityNotFoundException(propName); + } + + CustomModelProperty modifiedProp = newProperties.get(propName); + if (modifiedProp == null) + { + throw new InvalidArgumentException("cmm.rest_api.property_update_prop_not_found", new Object[] { propName }); + } + + existingProp.setTitle(modifiedProp.getTitle()); + existingProp.setDescription(modifiedProp.getDescription()); + existingProp.setDefaultValue(modifiedProp.getDefaultValue()); + existingProp.setConstraintRefs(modifiedProp.getConstraintRefs()); + existingProp.setConstraints(modifiedProp.getConstraints()); + if (isModelActive) + { + validateActivePropertyUpdate(existingProp, modifiedProp); + } + existingProp.setDataType(modifiedProp.getDataType()); + existingProp.setMandatory(modifiedProp.isMandatory()); + existingProp.setMandatoryEnforced(modifiedProp.isMandatoryEnforced()); + existingProp.setMultiValued(modifiedProp.isMultiValued()); + } + // Override properties + existingProperties.putAll(newProperties); + existingDetails.setProperties(new ArrayList<>(existingProperties.values())); + } + + /** + * A helper method to throw a more informative exception (for an active model) rather than depending on the + * {@link org.alfresco.repo.dictionary.ModelValidatorImpl#validateModel} + * generic exception. + */ + private void validateActivePropertyUpdate(CustomModelProperty existingProp, CustomModelProperty newProp) + { + if (!StringUtils.equals(existingProp.getDataType(), newProp.getDataType())) + { + throw new ConstraintViolatedException("cmm.rest_api.property_change_datatype_err"); + } + if (existingProp.isMandatory() != newProp.isMandatory()) + { + throw new ConstraintViolatedException("cmm.rest_api.property_change_mandatory_opt_err"); + } + if (existingProp.isMandatoryEnforced() != newProp.isMandatoryEnforced()) + { + throw new ConstraintViolatedException("cmm.rest_api.property_change_mandatory_enforced_opt_err"); + } + if (existingProp.isMultiValued() != newProp.isMultiValued()) + { + throw new ConstraintViolatedException("cmm.rest_api.property_change_multi_valued_opt_err"); + } + } + + private void deleteProperty(AbstractClassModel existingClassModel, String propertyName) + { + // Transform existing properties into a map + Map existingProperties = transformToMap(existingClassModel.getProperties(), toNameFunction()); + if (!existingProperties.containsKey(propertyName)) + { + throw new EntityNotFoundException(propertyName); + } + existingProperties.remove(propertyName); + existingClassModel.setProperties(new ArrayList<>(existingProperties.values())); + } + + private void validateList(List list, String errorMsg) + { + if (CollectionUtils.isEmpty(list)) + { + throw new InvalidArgumentException(errorMsg); + } + } + + private static Map transformToMap(Collection collection, Function function) + { + Map map = new HashMap<>(collection.size()); + + for (V item : collection) + { + map.put(function.apply(item), item); + } + return map; + } + + private static Map removeRightEntries(Map leftMap, Map rightMap) + { + Map result = new HashMap<>(leftMap); + for (K key : rightMap.keySet()) + { + result.remove(key); + } + return result; + } + + private void validateTypeAspectDelete(Collection list, String classPrefixedName) + { + for(AbstractClassModel acm : list) + { + if(classPrefixedName.equals(acm.getParentName())) + { + throw new ConstraintViolatedException(I18NUtil.getMessage("cmm.rest_api.aspect_type_cannot_delete", classPrefixedName, acm.getPrefixedName())); + } + } + } + + private boolean hasSelectProperty(Parameters parameters, String param) + { + return parameters.getSelectedProperties().contains(param); + } + + private static Function toNameFunction() + { + return new Function() + { + @Override + public String apply(AbstractCommonDetails details) + { + return details.getName(); + } + }; + } + + private T getObjectByName(Collection collection, String name) + { + for (T details : collection) + { + if (details.getName().equals(name)) + { + return details; + } + } + return null; + } + + public class ModelDetails + { + private CustomModel model; + private boolean active; + private List types; + private List aspects; + private List modelDefinedConstraints; + + public ModelDetails(CustomModelDefinition modelDefinition) + { + this.model = new CustomModel(modelDefinition); + this.active = modelDefinition.isActive(); + this.types = convertToCustomTypes(modelDefinition.getTypeDefinitions(), false); + this.aspects = convertToCustomAspects(modelDefinition.getAspectDefinitions(), false); + this.modelDefinedConstraints = convertToCustomModelConstraints(modelDefinition.getModelDefinedConstraints()); + } + + public CustomModel getModel() + { + return this.model; + } + + public void setModel(CustomModel model) + { + this.model = model; + } + + public List getTypes() + { + return this.types; + } + + public void setTypes(List types) + { + this.types = types; + } + + public List getAspects() + { + return this.aspects; + } + + public void setAspects(List aspects) + { + this.aspects = aspects; + } + + public List getModelDefinedConstraints() + { + return this.modelDefinedConstraints; + } + + public void setModelDefinedConstraints(List modelDefinedConstraints) + { + this.modelDefinedConstraints = modelDefinedConstraints; + } + + public boolean isActive() + { + return this.active; + } + } + + /** + * Constraint validator + * + * @author Jamal Kaabi-Mofrad + */ + public enum ConstraintValidator + { + REGEX + { + @Override + public void validate(String parameterName, String value) + { + if ("expression".equals(parameterName)) + { + try + { + Pattern.compile(value); + } + catch (Exception ex) + { + throw new InvalidArgumentException("cmm.rest_api.regex_constraint_invalid_expression", new Object[] { value }); + } + } + } + }, + MINMAX + { + @Override + public void validate(String parameterName, String value) + { + double parsedValue; + try + { + parsedValue = Double.parseDouble(value); + } + catch (Exception ex) + { + throw new InvalidArgumentException("cmm.rest_api.minmax_constraint_invalid_parameter", new Object[] { value, parameterName }); + } + // SHA-1126. We check for the Double.MIN_VALUE to be consistent with NumericRangeConstraint.minValue + if("maxValue".equalsIgnoreCase(parameterName) && parsedValue < Double.MIN_VALUE) + { + throw new InvalidArgumentException("cmm.rest_api.minmax_constraint_invalid_max_value"); + } + } + + @Override + public void validateUsage(QName propDataType) + { + if (propDataType != null && !(DataTypeDefinition.INT.equals(propDataType) + || DataTypeDefinition.LONG.equals(propDataType) + || DataTypeDefinition.FLOAT.equals(propDataType) + || DataTypeDefinition.DOUBLE.equals(propDataType))) + { + throw new InvalidArgumentException("cmm.rest_api.minmax_constraint_invalid_use"); + } + } + }, + LENGTH + { + @Override + public void validate(String parameterName, String value) + { + try + { + Integer.parseInt(value); + } + catch (Exception ex) + { + throw new InvalidArgumentException("cmm.rest_api.length_constraint_invalid_parameter", new Object[] { value, parameterName }); + } + } + + @Override + public void validateUsage(QName propDataType) + { + if (propDataType != null && !(DataTypeDefinition.TEXT.equals(propDataType) + || DataTypeDefinition.MLTEXT.equals(propDataType) + || DataTypeDefinition.CONTENT.equals(propDataType))) + { + throw new InvalidArgumentException("cmm.rest_api.length_constraint_invalid_use"); + } + } + }, + DUMMY_CONSTRAINT + { + @Override + public void validate(String parameterName, String value) + { + // nothing to do + } + }; + + public abstract void validate(String parameterName, String value); + + public void validateUsage(QName propDataType) + { + return; // nothing to do + } + + public static ConstraintValidator findByType(String constraintType) + { + for (ConstraintValidator c : values()) + { + if (c.name().equals(constraintType)) + { + return c; + } + } + return DUMMY_CONSTRAINT; + } + } + + @Override + public CustomModel createCustomModel(M2Model m2Model) + { + // Check the current user is authorised to import the custom model + validateCurrentUser(); + + validateImportedM2Model(m2Model); + + CompiledModel compiledModel = null; + try + { + compiledModel = customModelService.compileModel(m2Model); + } + catch (CustomModelConstraintException mce) + { + throw new ConstraintViolatedException(mce.getMessage()); + } + catch (InvalidCustomModelException iex) + { + throw new InvalidArgumentException(iex.getMessage()); + } + + ModelDefinition modelDefinition = compiledModel.getModelDefinition(); + CustomModel customModel = new CustomModel(); + customModel.setName(modelDefinition.getName().getLocalName()); + customModel.setAuthor(modelDefinition.getAuthor()); + customModel.setDescription(modelDefinition.getDescription(dictionaryService)); + customModel.setStatus(ModelStatus.DRAFT); + NamespaceDefinition nsd = modelDefinition.getNamespaces().iterator().next(); + customModel.setNamespaceUri(nsd.getUri()); + customModel.setNamespacePrefix(nsd.getPrefix()); + + List customTypes = convertToCustomTypes(compiledModel.getTypes(), false); + List customAspects = convertToCustomAspects(compiledModel.getAspects(), false); + + List constraintDefinitions = CustomModelDefinitionImpl.removeInlineConstraints(compiledModel); + List customModelConstraints = convertToCustomModelConstraints(constraintDefinitions); + + customModel.setTypes(customTypes); + customModel.setAspects(customAspects); + customModel.setConstraints(customModelConstraints); + + return createCustomModelImpl(customModel, false); + } + + private void validateImportedM2Model(M2Model m2Model) + { + List namespaces = m2Model.getNamespaces(); + if (namespaces.size() > 1) + { + throw new ConstraintViolatedException(I18NUtil.getMessage("cmm.rest_api.model.import_namespace_multiple_found", namespaces.size())); + } + else if (namespaces.isEmpty()) + { + throw new ConstraintViolatedException("cmm.rest_api.model.import_namespace_undefined"); + } + + checkUnsupportedModelElements(m2Model.getTypes()); + checkUnsupportedModelElements(m2Model.getAspects()); + } + + private void checkUnsupportedModelElements(Collection m2Classes) + { + for (M2Class cls : m2Classes) + { + if (cls.getAssociations().size() > 0) + { + throw new ConstraintViolatedException("cmm.rest_api.model.import_associations_unsupported"); + } + if (cls.getPropertyOverrides().size() > 0) + { + throw new ConstraintViolatedException("cmm.rest_api.model.import_overrides_unsupported"); + } + if (cls.getMandatoryAspects().size() > 0) + { + throw new ConstraintViolatedException("cmm.rest_api.model.import_mandatory_aspects_unsupported"); + } + if(cls.getArchive() != null) + { + throw new ConstraintViolatedException("cmm.rest_api.model.import_archive_unsupported"); + } + if(cls.getIncludedInSuperTypeQuery() != null) + { + throw new ConstraintViolatedException("cmm.rest_api.model.import_includedInSuperTQ_unsupported"); + } + } + } +} diff --git a/source/java/org/alfresco/rest/api/model/AbstractClassModel.java b/source/java/org/alfresco/rest/api/model/AbstractClassModel.java new file mode 100644 index 0000000000..3acd9adf84 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/AbstractClassModel.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.alfresco.service.namespace.QName; + +/** + * @author Jamal Kaabi-Mofrad + */ +public abstract class AbstractClassModel extends AbstractCommonDetails +{ + /* package */String parentName; + /* package */List properties = Collections.emptyList(); + + public String getParentName() + { + return this.parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public List getProperties() + { + return this.properties; + } + + public void setProperties(List properties) + { + this.properties = properties; + } + + /* package */ List setList(List sourceList) + { + if (sourceList == null) + { + return Collections. emptyList(); + } + return new ArrayList<>(sourceList); + } + + /* package */String getParentNameAsString(QName parentQName) + { + if (parentQName != null) + { + return parentQName.toPrefixString(); + } + return null; + } +} diff --git a/source/java/org/alfresco/rest/api/model/AbstractCommonDetails.java b/source/java/org/alfresco/rest/api/model/AbstractCommonDetails.java new file mode 100644 index 0000000000..edb0f76bee --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/AbstractCommonDetails.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +/** + * @author Jamal Kaabi-Mofrad + */ +public abstract class AbstractCommonDetails implements Comparable +{ + /* package */String name; + /* package */String prefixedName; + /* package */String title; + /* package */String description; + + public String getName() + { + return this.name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPrefixedName() + { + return this.prefixedName; + } + + public String getTitle() + { + return this.title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getDescription() + { + return this.description; + } + + public void setDescription(String description) + { + this.description = description; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof CustomModelConstraint)) + { + return false; + } + CustomModelConstraint other = (CustomModelConstraint) obj; + if (this.name == null) + { + if (other.name != null) + { + return false; + } + } + else if (!this.name.equals(other.name)) + { + return false; + } + return true; + } + + @Override + public int compareTo(AbstractCommonDetails other) + { + return this.name.compareTo(other.getName()); + } +} diff --git a/source/java/org/alfresco/rest/api/model/CustomAspect.java b/source/java/org/alfresco/rest/api/model/CustomAspect.java new file mode 100644 index 0000000000..65a07a1bf3 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomAspect.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.List; + +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.i18n.MessageLookup; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomAspect extends AbstractClassModel +{ + + public CustomAspect() + { + } + + public CustomAspect(AspectDefinition aspectDefinition, MessageLookup messageLookup, List properties) + { + this.name = aspectDefinition.getName().getLocalName(); + this.prefixedName = aspectDefinition.getName().toPrefixString(); + this.title = aspectDefinition.getTitle(messageLookup); + this.description = aspectDefinition.getDescription(messageLookup); + this.parentName = getParentNameAsString(aspectDefinition.getParentName()); + this.properties = setList(properties); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(512); + builder.append("CustomAspect [name=").append(this.name) + .append(", prefixedName=").append(this.prefixedName) + .append(", title=").append(this.title) + .append(", description=").append(this.description) + .append(", parentName=").append(parentName) + .append(", properties=").append(properties) + .append(']'); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/rest/api/model/CustomModel.java b/source/java/org/alfresco/rest/api/model/CustomModel.java new file mode 100644 index 0000000000..35e7852da6 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomModel.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.List; + +import org.alfresco.service.cmr.dictionary.CustomModelDefinition; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomModel implements Comparable +{ + public static enum ModelStatus + { + ACTIVE, DRAFT + } + + private String name; + private String author; + private String description; + private ModelStatus status; + private String namespaceUri; + private String namespacePrefix; + private List types; + private List aspects; + private List constraints; + + public CustomModel() + { + } + + public CustomModel(CustomModelDefinition modelDefinition) + { + this(modelDefinition, null, null, null); + } + + public CustomModel(CustomModelDefinition modelDefinition, List types, List aspects, List constraints) + { + this.name = modelDefinition.getName().getLocalName(); + this.author = modelDefinition.getAuthor(); + this.description = modelDefinition.getDescription(); + this.status = modelDefinition.isActive() ? ModelStatus.ACTIVE : ModelStatus.DRAFT; + // we don't need to check for NoSuchElementException, as we don't allow + // the model to be saved without a valid namespace + NamespaceDefinition nsd = modelDefinition.getNamespaces().iterator().next(); + this.namespaceUri = nsd.getUri(); + this.namespacePrefix = nsd.getPrefix(); + this.types = types; + this.aspects = aspects; + this.constraints = constraints; + } + + public String getName() + { + return this.name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getAuthor() + { + return this.author; + } + + public void setAuthor(String author) + { + this.author = author; + } + + public String getDescription() + { + return this.description; + } + + public void setDescription(String description) + { + this.description = description; + } + + public ModelStatus getStatus() + { + return this.status; + } + + public void setStatus(ModelStatus status) + { + this.status = status; + } + + public String getNamespaceUri() + { + return this.namespaceUri; + } + + public void setNamespaceUri(String namespaceUri) + { + this.namespaceUri = namespaceUri; + } + + public String getNamespacePrefix() + { + return this.namespacePrefix; + } + + public void setNamespacePrefix(String namespacePrefix) + { + this.namespacePrefix = namespacePrefix; + } + + public List getTypes() + { + return this.types; + } + + public void setTypes(List types) + { + this.types = types; + } + + public List getAspects() + { + return this.aspects; + } + + public void setAspects(List aspects) + { + this.aspects = aspects; + } + + public List getConstraints() + { + return this.constraints; + } + + public void setConstraints(List constraints) + { + this.constraints = constraints; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof CustomModel)) + { + return false; + } + CustomModel other = (CustomModel) obj; + if (this.name == null) + { + if (other.name != null) + { + return false; + } + } + else if (!this.name.equals(other.name)) + { + return false; + } + return true; + } + + @Override + public int compareTo(CustomModel customModel) + { + return this.name.compareTo(customModel.getName()); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(512); + builder.append("CustomModel [name=").append(this.name).append(", author=").append(this.author) + .append(", description=").append(this.description).append(", status=").append(this.status) + .append(", namespaceUri=").append(this.namespaceUri).append(", namespacePrefix=") + .append(this.namespacePrefix).append(']'); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/rest/api/model/CustomModelConstraint.java b/source/java/org/alfresco/rest/api/model/CustomModelConstraint.java new file mode 100644 index 0000000000..481e025c20 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomModelConstraint.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.i18n.MessageLookup; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelConstraint extends AbstractCommonDetails +{ + private String type; + private List parameters = Collections.emptyList(); + + public CustomModelConstraint() + { + } + + public CustomModelConstraint(ConstraintDefinition constraintDefinition, MessageLookup messageLookup) + { + this.name = constraintDefinition.getName().getLocalName(); + this.prefixedName = constraintDefinition.getConstraint().getShortName(); + this.type = constraintDefinition.getConstraint().getType(); + this.title = constraintDefinition.getTitle(messageLookup); + this.description = constraintDefinition.getDescription(messageLookup); + this.parameters = convertToNamedValue(constraintDefinition.getConstraint().getParameters()); + } + + private List convertToNamedValue(Map params) + { + List list = new ArrayList<>(params.size()); + for (Entry en : params.entrySet()) + { + list.add(new CustomModelNamedValue(en.getKey(), en.getValue())); + } + + return list; + } + + public String getType() + { + return this.type; + } + + public void setType(String type) + { + this.type = type; + } + + public List getParameters() + { + return this.parameters; + } + + public void setParameters(List parameters) + { + this.parameters = parameters; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(300); + builder.append("CustomModelConstraint [name=").append(this.name) + .append(", prefixedName=").append(this.prefixedName) + .append(", type=").append(this.type) + .append(", title=").append(this.title) + .append(", description=").append(this.description) + .append(", parameters=").append(this.parameters) + .append(']'); + return builder.toString(); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/model/CustomModelDownload.java b/source/java/org/alfresco/rest/api/model/CustomModelDownload.java new file mode 100644 index 0000000000..99cee5234c --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomModelDownload.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelDownload implements Comparable +{ + private String nodeRef; + + public CustomModelDownload() + { + } + + public CustomModelDownload(NodeRef nodeRef) + { + this.nodeRef = nodeRef.toString(); + } + + public String getNodeRef() + { + return this.nodeRef; + } + + public void setNodeRef(String nodeRef) + { + this.nodeRef = nodeRef; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.nodeRef == null) ? 0 : this.nodeRef.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof CustomModelDownload)) + { + return false; + } + CustomModelDownload other = (CustomModelDownload) obj; + if (this.nodeRef == null) + { + if (other.nodeRef != null) + { + return false; + } + } + else if (!this.nodeRef.equals(other.nodeRef)) + { + return false; + } + return true; + } + + @Override + public int compareTo(CustomModelDownload other) + { + return this.nodeRef.toString().compareTo(other.getNodeRef().toString()); + } +} diff --git a/source/java/org/alfresco/rest/api/model/CustomModelNamedValue.java b/source/java/org/alfresco/rest/api/model/CustomModelNamedValue.java new file mode 100644 index 0000000000..a2c4b26f6e --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomModelNamedValue.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.repository.datatype.TypeConversionException; + +/** + * + * + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelNamedValue implements Comparable +{ + private String name; + private String simpleValue = null; + private List listValue = null; + + public CustomModelNamedValue() + { + } + + public CustomModelNamedValue(String name, Object value) + { + this.name = name; + if (value instanceof List) + { + List values = (List) value; + listValue = new ArrayList<>(values.size()); + for(Object val : values) + { + listValue.add(convertToString(val)); + } + } + else + { + simpleValue = convertToString(value); + } + } + + private String convertToString(Object value) + { + try + { + return DefaultTypeConverter.INSTANCE.convert(String.class, value); + } + catch (TypeConversionException e) + { + throw new InvalidArgumentException("Cannot convert to string '" + value + "'."); + } + } + + public String getName() + { + return this.name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getSimpleValue() + { + return this.simpleValue; + } + + public void setSimpleValue(String simpleValue) + { + this.simpleValue = simpleValue; + } + + public List getListValue() + { + return this.listValue; + } + + public void setListValue(List listValue) + { + this.listValue = listValue; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof CustomModelNamedValue)) + { + return false; + } + CustomModelNamedValue other = (CustomModelNamedValue) obj; + if (this.name == null) + { + if (other.name != null) + { + return false; + } + } + else if (!this.name.equals(other.name)) + { + return false; + } + return true; + } + + @Override + public int compareTo(CustomModelNamedValue other) + { + return name.compareTo(other.getName()); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(120); + builder.append("CustomModelNamedValue [name=").append(this.name) + .append(", simpleValue=").append(this.simpleValue) + .append(", listValue=").append(this.listValue) + .append(']'); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/rest/api/model/CustomModelProperty.java b/source/java/org/alfresco/rest/api/model/CustomModelProperty.java new file mode 100644 index 0000000000..5ae137d9d1 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomModelProperty.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.alfresco.repo.dictionary.Facetable; +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.i18n.MessageLookup; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelProperty extends AbstractCommonDetails +{ + private String dataType; + private boolean isMandatory; + private boolean isMandatoryEnforced; + private boolean isMultiValued; + private String defaultValue; + private boolean isIndexed = true; + private Facetable facetable = Facetable.UNSET; + private IndexTokenisationMode indexTokenisationMode; + private List constraintRefs = Collections.emptyList(); + private List constraints = Collections.emptyList(); + + public CustomModelProperty() + { + } + + public CustomModelProperty(PropertyDefinition propertyDefinition, MessageLookup messageLookup) + { + this.name = propertyDefinition.getName().getLocalName(); + this.prefixedName = propertyDefinition.getName().toPrefixString(); + this.title = propertyDefinition.getTitle(messageLookup); + this.dataType = propertyDefinition.getDataType().getName().toPrefixString(); + this.description = propertyDefinition.getDescription(messageLookup); + this.isMandatory = propertyDefinition.isMandatory(); + this.isMandatoryEnforced = propertyDefinition.isMandatoryEnforced(); + this.isMultiValued = propertyDefinition.isMultiValued(); + this.defaultValue = propertyDefinition.getDefaultValue(); + this.isIndexed = propertyDefinition.isIndexed(); + this.facetable = propertyDefinition.getFacetable(); + this.indexTokenisationMode = propertyDefinition.getIndexTokenisationMode(); + List constraintDefs = propertyDefinition.getConstraints(); + if (constraintDefs.size() > 0) + { + this.constraintRefs = new ArrayList<>(); + this.constraints = new ArrayList<>(); + for (ConstraintDefinition cd : constraintDefs) + { + if (cd.getRef() != null) + { + constraintRefs.add(cd.getRef().toPrefixString()); + } + else + { + constraints.add(new CustomModelConstraint(cd, messageLookup)); + } + } + } + } + + public String getDataType() + { + return this.dataType; + } + + public void setDataType(String dataType) + { + this.dataType = dataType; + } + + public boolean isMandatory() + { + return this.isMandatory; + } + + public void setMandatory(boolean isMandatory) + { + this.isMandatory = isMandatory; + } + + public boolean isMandatoryEnforced() + { + return this.isMandatoryEnforced; + } + + public void setMandatoryEnforced(boolean isMandatoryEnforced) + { + this.isMandatoryEnforced = isMandatoryEnforced; + } + + public boolean isMultiValued() + { + return this.isMultiValued; + } + + public void setMultiValued(boolean isMultiValued) + { + this.isMultiValued = isMultiValued; + } + + public String getDefaultValue() + { + return this.defaultValue; + } + + public void setDefaultValue(String defaultValue) + { + this.defaultValue = defaultValue; + } + + public boolean isIndexed() + { + return this.isIndexed; + } + + public void setIndexed(boolean isIndexed) + { + this.isIndexed = isIndexed; + } + + public Facetable getFacetable() + { + return this.facetable; + } + + public void setFacetable(Facetable facetable) + { + this.facetable = facetable; + } + + public IndexTokenisationMode getIndexTokenisationMode() + { + return this.indexTokenisationMode; + } + + public void setIndexTokenisationMode(IndexTokenisationMode indexTokenisationMode) + { + this.indexTokenisationMode = indexTokenisationMode; + } + + public List getConstraintRefs() + { + return this.constraintRefs; + } + + public void setConstraintRefs(List constraintRefs) + { + this.constraintRefs = constraintRefs; + } + + public List getConstraints() + { + return this.constraints; + } + + public void setConstraints(List constraints) + { + this.constraints = constraints; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(612); + builder.append("CustomModelProperty [name=").append(this.name) + .append(", prefixedName=").append(this.prefixedName) + .append(", title=").append(this.title) + .append(", description=").append(this.description) + .append(", dataType=").append(this.dataType) + .append(", isMandatory=").append(this.isMandatory) + .append(", isMandatoryEnforced=").append(this.isMandatoryEnforced) + .append(", isMultiValued=").append(this.isMultiValued) + .append(", defaultValue=").append(this.defaultValue) + .append(", isIndexed=").append(this.isIndexed) + .append(", facetable=").append(this.facetable) + .append(", indexTokenisationMode=").append(this.indexTokenisationMode) + .append(", constraintRefs=").append(this.constraintRefs) + .append(", constraints=").append(this.constraints) + .append(']'); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/rest/api/model/CustomType.java b/source/java/org/alfresco/rest/api/model/CustomType.java new file mode 100644 index 0000000000..4e9f0e9d71 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/CustomType.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.model; + +import java.util.List; + +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.i18n.MessageLookup; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CustomType extends AbstractClassModel +{ + + public CustomType() + { + } + + public CustomType(TypeDefinition typeDefinition, MessageLookup messageLookup, List properties) + { + this.name = typeDefinition.getName().getLocalName(); + this.prefixedName = typeDefinition.getName().toPrefixString(); + this.title = typeDefinition.getTitle(messageLookup); + this.description = typeDefinition.getDescription(messageLookup); + this.parentName = getParentNameAsString(typeDefinition.getParentName()); + this.properties = setList(properties); + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(512); + builder.append("CustomType [name=").append(this.name) + .append(", prefixedName=").append(this.prefixedName) + .append(", title=").append(this.title) + .append(", description=").append(this.description) + .append(", parentName=").append(parentName) + .append(", properties=").append(properties) + .append(']'); + return builder.toString(); + } +} diff --git a/source/test-java/org/alfresco/RemoteApi01TestSuite.java b/source/test-java/org/alfresco/RemoteApi01TestSuite.java index 642670e5c5..13a3e80117 100644 --- a/source/test-java/org/alfresco/RemoteApi01TestSuite.java +++ b/source/test-java/org/alfresco/RemoteApi01TestSuite.java @@ -93,4 +93,9 @@ public class RemoteApi01TestSuite extends TestSuite suite.addTest(new JUnit4TestAdapter(org.alfresco.rest.workflow.api.tests.ProcessWorkflowApiTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.rest.workflow.api.tests.TaskWorkflowApiTest.class)); } + + static void tests8(TestSuite suite) // + { + suite.addTest(org.alfresco.rest.api.tests.CMMApiTestSuite.suite()); + } } \ No newline at end of file diff --git a/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java b/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java index ac1177e144..04b99c8c5d 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java +++ b/source/test-java/org/alfresco/repo/web/scripts/WebScriptTestSuite.java @@ -28,6 +28,7 @@ import org.alfresco.repo.web.scripts.admin.AdminWebScriptTest; import org.alfresco.repo.web.scripts.audit.AuditWebScriptTest; import org.alfresco.repo.web.scripts.blogs.BlogServiceTest; import org.alfresco.repo.web.scripts.comment.CommentsApiTest; +import org.alfresco.repo.web.scripts.custommodel.CustomModelImportTest; import org.alfresco.repo.web.scripts.dictionary.DictionaryRestApiTest; import org.alfresco.repo.web.scripts.discussion.DiscussionRestApiTest; import org.alfresco.repo.web.scripts.facet.FacetRestApiTest; @@ -98,12 +99,13 @@ public class WebScriptTestSuite extends TestSuite suite.addTestSuite( SOLRWebScriptTest.class ); suite.addTestSuite( SubscriptionServiceRestApiTest.class ); suite.addTestSuite( FacetRestApiTest.class ); - suite.addTestSuite( CommentsApiTest.class ); + suite.addTestSuite( CommentsApiTest.class ); suite.addTestSuite( DeclarativeSpreadsheetWebScriptTest.class ); suite.addTestSuite( XssVulnerabilityTest.class ); suite.addTestSuite( LinksRestApiTest.class ); suite.addTestSuite( RemoteFileFolderLoaderTest.class ); suite.addTestSuite( ReadOnlyTransactionInGetRestApiTest.class ); + suite.addTestSuite( CustomModelImportTest.class ); // This uses a slightly different context // As such, we can't run it in the same suite as the others, // due to finalisers closing caches when we're not looking diff --git a/source/test-java/org/alfresco/repo/web/scripts/custommodel/CustomModelImportTest.java b/source/test-java/org/alfresco/repo/web/scripts/custommodel/CustomModelImportTest.java new file mode 100644 index 0000000000..512e22dcf5 --- /dev/null +++ b/source/test-java/org/alfresco/repo/web/scripts/custommodel/CustomModelImportTest.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.web.scripts.custommodel; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.CustomModelServiceImpl; +import org.alfresco.repo.dictionary.M2Association; +import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.dictionary.M2Type; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyMap; +import org.alfresco.util.TempFileProvider; +import org.alfresco.util.XMLUtil; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; +import org.springframework.util.ResourceUtils; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +/** + * This class tests the custom model upload REST API. + * + * @author Jamal Kaabi-Mofrad + */ +public class CustomModelImportTest extends BaseWebScriptTest +{ + private static final String NON_ADMIN_USER = "nonAdminUserName"; + private static final String CUSTOM_MODEL_ADMIN = "customModelAdmin"; + private static final String RESOURCE_PREFIX = "custommodel/"; + private static final String UPLOAD_URL = "/api/cmm/upload"; + private static final int BUFFER_SIZE = 20 * 1024; + + private MutableAuthenticationService authenticationService; + private AuthorityService authorityService; + private PersonService personService; + private RetryingTransactionHelper transactionHelper; + private CustomModelService customModelService; + private List importedModels = new ArrayList<>(); + private List tempFiles = new ArrayList<>(); + + @Override + protected void setUp() throws Exception + { + super.setUp(); + authenticationService = getServer().getApplicationContext().getBean("AuthenticationService", MutableAuthenticationService.class); + authorityService = getServer().getApplicationContext().getBean("AuthorityService", AuthorityService.class); + personService = getServer().getApplicationContext().getBean("PersonService", PersonService.class); + transactionHelper = getServer().getApplicationContext().getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + customModelService = getServer().getApplicationContext().getBean("customModelService", CustomModelService.class); + + AuthenticationUtil.clearCurrentSecurityContext(); + + AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + createUser(NON_ADMIN_USER); + createUser(CUSTOM_MODEL_ADMIN); + + if (!authorityService.getContainingAuthorities(AuthorityType.GROUP, CUSTOM_MODEL_ADMIN, true).contains( + CustomModelServiceImpl.GROUP_ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY)) + { + authorityService.addAuthority(CustomModelServiceImpl.GROUP_ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY, CUSTOM_MODEL_ADMIN); + } + return null; + } + }); + AuthenticationUtil.setFullyAuthenticatedUser(CUSTOM_MODEL_ADMIN); + } + + @Override + public void tearDown() throws Exception + { + for (File file : tempFiles) + { + file.delete(); + } + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for (String modelName : importedModels) + { + customModelService.deleteCustomModel(modelName); + } + return null; + } + }); + + AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + deleteUser(NON_ADMIN_USER); + deleteUser(CUSTOM_MODEL_ADMIN); + return null; + } + }); + return null; + } + }); + + AuthenticationUtil.clearCurrentSecurityContext(); + + super.tearDown(); + } + + public void testValidUpload_ModelAndExtModule() throws Exception + { + File zipFile = getResourceFile("validModelAndExtModule.zip"); + PostRequest postRequest = buildMultipartPostRequest(zipFile); + + AuthenticationUtil.setFullyAuthenticatedUser(NON_ADMIN_USER); + Response response = sendRequest(postRequest, 403); + + AuthenticationUtil.setFullyAuthenticatedUser(CUSTOM_MODEL_ADMIN); + response = sendRequest(postRequest, 200); + + JSONObject json = new JSONObject(new JSONTokener(response.getContentAsString())); + + String importedModelName = json.getString("modelName"); + importedModels.add(importedModelName); + + String extModule = json.getString("shareExtModule"); + Document document = XMLUtil.parse(extModule); + NodeList nodes = document.getElementsByTagName("id"); + assertEquals(1, nodes.getLength()); + assertNotNull(nodes.item(0).getTextContent()); + } + + public void testValidUpload_ModelOnly() throws Exception + { + File zipFile = getResourceFile("validModel.zip"); + PostRequest postRequest = buildMultipartPostRequest(zipFile); + + AuthenticationUtil.setFullyAuthenticatedUser(NON_ADMIN_USER); + Response response = sendRequest(postRequest, 403); + + AuthenticationUtil.setFullyAuthenticatedUser(CUSTOM_MODEL_ADMIN); + response = sendRequest(postRequest, 200); + + JSONObject json = new JSONObject(new JSONTokener(response.getContentAsString())); + String importedModelName = json.getString("modelName"); + importedModels.add(importedModelName); + + assertFalse(json.has("shareExtModule")); + + // Import the same model again + sendRequest(postRequest, 409); // name conflict + } + + public void testValidUpload_ExtModuleOnly() throws Exception + { + File zipFile = getResourceFile("validExtModule.zip"); + PostRequest postRequest = buildMultipartPostRequest(zipFile); + + AuthenticationUtil.setFullyAuthenticatedUser(NON_ADMIN_USER); + Response response = sendRequest(postRequest, 403); + + AuthenticationUtil.setFullyAuthenticatedUser(CUSTOM_MODEL_ADMIN); + response = sendRequest(postRequest, 200); + + JSONObject json = new JSONObject(new JSONTokener(response.getContentAsString())); + assertFalse(json.has("modelName")); + + String extModule = json.getString("shareExtModule"); + Document document = XMLUtil.parse(extModule); + NodeList nodes = document.getElementsByTagName("id"); + assertEquals(1, nodes.getLength()); + assertNotNull(nodes.item(0).getTextContent()); + } + + public void testNotZipFileUpload() throws Exception + { + File file = getResourceFile("validModel.zip"); + ZipFile zipFile = new ZipFile(file); + ZipEntry zipEntry = zipFile.entries().nextElement(); + + File unzippedModelFile = TempFileProvider.createTempFile(zipFile.getInputStream(zipEntry), "validModel", ".xml"); + tempFiles.add(unzippedModelFile); + zipFile.close(); + + PostRequest postRequest = buildMultipartPostRequest(unzippedModelFile); + sendRequest(postRequest, 400); // CMM upload supports only zip file. + } + + public void testInvalidZipUpload() throws Exception + { + String content = "" + + "Jane" + + "John" + + "Upload test" + + "This is an invalid model or a Share extension module" + +""; + + ZipEntryContext context = new ZipEntryContext("invalidFormat.xml", content.getBytes()); + File zipFile = createZip(context); + + PostRequest postRequest = buildMultipartPostRequest(zipFile); + sendRequest(postRequest, 400); // Invalid. Neither a model nor a Share extension module file + } + + public void testUploadModel_Invalid() throws Exception + { + long timestamp = System.currentTimeMillis(); + final String modelName = getClass().getSimpleName() + timestamp; + final String prefix = "prefix" + timestamp; + final String uri = "uriNamespace" + timestamp; + + M2Model model = M2Model.createModel(prefix + QName.NAMESPACE_PREFIX + modelName); + model.setAuthor("Admin"); + model.setDescription("Desc"); + + ByteArrayOutputStream xml = new ByteArrayOutputStream(); + model.toXML(xml); + ZipEntryContext context = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + File zipFile = createZip(context); + + PostRequest postRequest = buildMultipartPostRequest(zipFile); + sendRequest(postRequest, 409); // no namespace has been defined + + // Create two namespaces + model.createNamespace(uri, prefix); + model.createNamespace(uri + "anotherUri", prefix + "anotherPrefix"); + xml = new ByteArrayOutputStream(); + model.toXML(xml); + context = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + zipFile = createZip(context); + + postRequest = buildMultipartPostRequest(zipFile); + sendRequest(postRequest, 409); // custom model can only have one namespace + } + + public void testUploadModel_UnsupportedModelElements() throws Exception + { + // Note: here we only test a couple of not-supported model elements to check for the correct status code. + // This test should be removed when we implement the required support + + long timestamp = System.currentTimeMillis(); + final String modelName = getClass().getSimpleName() + timestamp; + final String prefix = "prefix"+timestamp; + final String uri = "uriNamespace"+timestamp; + final String aspectName = prefix + QName.NAMESPACE_PREFIX + "testAspec"; + final String typeName = prefix + QName.NAMESPACE_PREFIX + "testType"; + final String associationName = prefix + QName.NAMESPACE_PREFIX + "testAssociation"; + + M2Model model = M2Model.createModel(prefix + QName.NAMESPACE_PREFIX + modelName); + model.createNamespace(uri, prefix); + model.setAuthor("John Doe"); + model.createAspect(aspectName); + model.createImport(NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.CONTENT_MODEL_PREFIX); + + M2Type type = model.createType(typeName); + // Add 'association' not supported yet. + M2Association association = type.createAssociation(associationName); + association.setSourceMandatory(false); + association.setSourceMany(false); + association.setTargetMandatory(false); + association.setTargetClassName("cm:content"); + + ByteArrayOutputStream xml = new ByteArrayOutputStream(); + model.toXML(xml); + ZipEntryContext context = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + File zipFile = createZip(context); + + PostRequest postRequest = buildMultipartPostRequest(zipFile); + sendRequest(postRequest, 409); // element is not supported yet + + type.removeAssociation(associationName); + // Add 'mandatory-aspect' not supported yet. + type.addMandatoryAspect(aspectName); + xml = new ByteArrayOutputStream(); + model.toXML(xml); + context = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + zipFile = createZip(context); + + postRequest = buildMultipartPostRequest(zipFile); + sendRequest(postRequest, 409); // element is not supported yet + } + + public void testInvalidNumberOfZipEntries() throws Exception + { + long timestamp = System.currentTimeMillis(); + String modelName = getClass().getSimpleName() + timestamp; + String prefix = "prefix" + timestamp; + String uri = "uriNamespace" + timestamp; + + // Model one + M2Model modelOne = M2Model.createModel(prefix + QName.NAMESPACE_PREFIX + modelName); + modelOne.createNamespace(uri, prefix); + modelOne.setDescription("Model 1"); + ByteArrayOutputStream xml = new ByteArrayOutputStream(); + modelOne.toXML(xml); + ZipEntryContext contextOne = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + + // Model two + modelName += "two"; + prefix += "two"; + uri += "two"; + M2Model modelTwo = M2Model.createModel(prefix + QName.NAMESPACE_PREFIX + modelName); + modelTwo.createNamespace(uri, prefix); + modelTwo.setDescription("Model 2"); + xml = new ByteArrayOutputStream(); + modelTwo.toXML(xml); + ZipEntryContext contextTwo = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + + // Model three + modelName += "three"; + prefix += "three"; + uri += "three"; + M2Model modelThree = M2Model.createModel(prefix + QName.NAMESPACE_PREFIX + modelName); + modelThree.createNamespace(uri, prefix); + modelThree.setDescription("Model 3"); + xml = new ByteArrayOutputStream(); + modelThree.toXML(xml); + ZipEntryContext contextThree = new ZipEntryContext(modelName + ".xml", xml.toByteArray()); + + File zipFile = createZip(contextOne, contextTwo, contextThree); + + PostRequest postRequest = buildMultipartPostRequest(zipFile); + sendRequest(postRequest, 400); // more than two zip entries + } + + public PostRequest buildMultipartPostRequest(File file) throws IOException + { + Part[] parts = { new FilePart("filedata", file.getName(), file, "application/zip", null) }; + + MultipartRequestEntity multipartRequestEntity = new MultipartRequestEntity(parts, new HttpMethodParams()); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + multipartRequestEntity.writeRequest(os); + + PostRequest postReq = new PostRequest(UPLOAD_URL, os.toByteArray(), multipartRequestEntity.getContentType()); + return postReq; + } + + private void createUser(String userName) + { + if (!authenticationService.authenticationExists(userName)) + { + authenticationService.createAuthentication(userName, "PWD".toCharArray()); + } + + if (!personService.personExists(userName)) + { + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + personService.createPerson(ppOne); + } + } + + private void deleteUser(String userName) + { + if (personService.personExists(userName)) + { + personService.deletePerson(userName); + } + } + + private File getResourceFile(String xmlFileName) throws FileNotFoundException + { + URL url = CustomModelImportTest.class.getClassLoader().getResource(RESOURCE_PREFIX + xmlFileName); + if (url == null) + { + fail("Cannot get the resource: " + xmlFileName); + } + return ResourceUtils.getFile(url); + } + + private File createZip(ZipEntryContext... zipEntryContexts) + { + File zipFile = TempFileProvider.createTempFile(getClass().getSimpleName(), ".zip"); + tempFiles.add(zipFile); + + byte[] buffer = new byte[BUFFER_SIZE]; + try + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(zipFile), BUFFER_SIZE); + ZipOutputStream zos = new ZipOutputStream(out); + + for (ZipEntryContext context : zipEntryContexts) + { + ZipEntry zipEntry = new ZipEntry(context.getZipEntryName()); + zos.putNextEntry(zipEntry); + + InputStream input = context.getEntryContent(); + int len; + while ((len = input.read(buffer)) > 0) + { + zos.write(buffer, 0, len); + } + input.close(); + } + zos.closeEntry(); + zos.close(); + } + catch (IOException ex) + { + fail("couldn't create zip file."); + } + + return zipFile; + } + + private static class ZipEntryContext + { + private final String zipEntryName; + private final InputStream entryContent; + + public ZipEntryContext(String zipEntryName, byte[] zipEntryContent) + { + this.zipEntryName = zipEntryName; + this.entryContent = new ByteArrayInputStream(zipEntryContent); + } + + public String getZipEntryName() + { + return this.zipEntryName; + } + + public InputStream getEntryContent() + { + return this.entryContent; + } + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/BaseCustomModelApiTest.java b/source/test-java/org/alfresco/rest/api/tests/BaseCustomModelApiTest.java new file mode 100644 index 0000000000..548f2d3285 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/BaseCustomModelApiTest.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.dictionary.CustomModelServiceImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.rest.api.model.AbstractClassModel; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModelConstraint; +import org.alfresco.rest.api.model.CustomModelNamedValue; +import org.alfresco.rest.api.model.CustomModelProperty; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.tests.RepoService.TestPerson; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.client.RequestContext; +import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import org.alfresco.service.cmr.dictionary.CustomModelDefinition; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.Pair; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.junit.After; +import org.junit.Before; + +/** + * Base class for CMM API tests + * + * @author Jamal Kaabi-Mofrad + */ +public class BaseCustomModelApiTest extends EnterpriseTestApi +{ + public static final String CMM_SCOPE = "private"; + public static final String SELECT_PROPS_QS = "?select=props"; + public static final String SELECT_STATUS_QS = "?select=status"; + public static final String SELECT_ALL = "?select=all"; + public static final String SELECT_ALL_PROPS = "?select=allProps"; + + protected String nonAdminUserName; + protected String customModelAdmin; + + protected MutableAuthenticationService authenticationService; + protected PersonService personService; + protected CustomModelService customModelService; + + private List users = new ArrayList<>(); + + @Before + public void setup() throws Exception + { + authenticationService = applicationContext.getBean("authenticationService", MutableAuthenticationService.class); + personService = applicationContext.getBean("personService", PersonService.class); + customModelService = applicationContext.getBean("customModelService", CustomModelService.class); + + final AuthorityService authorityService = applicationContext.getBean("authorityService", AuthorityService.class); + + this.nonAdminUserName = createUser("nonAdminUser" + System.currentTimeMillis()); + this.customModelAdmin = createUser("customModelAdmin" + System.currentTimeMillis()); + users.add(nonAdminUserName); + users.add(customModelAdmin); + // Add 'customModelAdmin' user into 'ALFRESCO_MODEL_ADMINISTRATORS' group + transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + authorityService.addAuthority(CustomModelServiceImpl.GROUP_ALFRESCO_MODEL_ADMINISTRATORS_AUTHORITY, customModelAdmin); + return null; + } + }); + } + + @After + public void tearDown() throws Exception + { + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + for (final String user : users) + { + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + authenticationService.deleteAuthentication(user); + personService.deletePerson(user); + return null; + } + }); + } + users.clear(); + AuthenticationUtil.clearCurrentSecurityContext(); + } + + protected String createUser(String username) + { + PersonInfo personInfo = new PersonInfo(username, username, username, "password", null, null, null, null, null, null, null); + TestPerson person = repoService.createUser(personInfo, username, null); + return person.getId(); + } + + protected HttpResponse post(String url, String runAsUser, String body, int expectedStatus) throws Exception + { + publicApiClient.setRequestContext(new RequestContext(runAsUser)); + + HttpResponse response = publicApiClient.post(CMM_SCOPE, url, null, null, null, body); + checkStatus(expectedStatus, response.getStatusCode()); + + return response; + } + + protected HttpResponse post(String url, String runAsUser, String body, String queryString, int expectedStatus) throws Exception + { + publicApiClient.setRequestContext(new RequestContext(runAsUser)); + if (queryString != null) + { + url += queryString; + } + HttpResponse response = publicApiClient.post(CMM_SCOPE, url, null, null, null, body); + checkStatus(expectedStatus, response.getStatusCode()); + + return response; + } + + protected HttpResponse getAll(String url, String runAsUser, Paging paging, int expectedStatus) throws Exception + { + publicApiClient.setRequestContext(new RequestContext(runAsUser)); + Map params = (paging == null) ? null : createParams(paging, null); + + HttpResponse response = publicApiClient.get(CMM_SCOPE, url, null, null, null, params); + checkStatus(expectedStatus, response.getStatusCode()); + + return response; + } + + protected HttpResponse getSingle(String url, String runAsUser, String entityId, int expectedStatus) throws Exception + { + publicApiClient.setRequestContext(new RequestContext(runAsUser)); + + HttpResponse response = publicApiClient.get(CMM_SCOPE, url, entityId, null, null, null); + checkStatus(expectedStatus, response.getStatusCode()); + + return response; + } + + protected HttpResponse put(String url, String runAsUser, String entityId, String body, String queryString, int expectedStatus) throws Exception + { + publicApiClient.setRequestContext(new RequestContext(runAsUser)); + if (queryString != null) + { + entityId += queryString; + } + HttpResponse response = publicApiClient.put(CMM_SCOPE, url, entityId, null, null, body, null); + checkStatus(expectedStatus, response.getStatusCode()); + + return response; + } + + protected HttpResponse delete(String url, String runAsUser, String entityId, int expectedStatus) throws Exception + { + publicApiClient.setRequestContext(new RequestContext(runAsUser)); + + HttpResponse response = publicApiClient.delete(CMM_SCOPE, url, entityId, null, null); + checkStatus(expectedStatus, response.getStatusCode()); + + return response; + } + + protected void checkStatus(int expectedStatus, int actualStatus) + { + if (expectedStatus > 0 && expectedStatus != actualStatus) + { + fail("Status code " + actualStatus + " returned, but expected " + expectedStatus); + } + } + + protected CustomModel createCustomModel(String modelName, Pair namespacePair, ModelStatus status) throws Exception + { + return createCustomModel(modelName, namespacePair, status, "Test model description", null); + } + + protected CustomModel createCustomModel(String modelName, Pair namespacePair, ModelStatus status, String desc, String author) + throws Exception + { + CustomModel customModel = new CustomModel(); + customModel.setName(modelName); + customModel.setNamespaceUri(namespacePair.getFirst()); + customModel.setNamespacePrefix(namespacePair.getSecond()); + customModel.setDescription(desc); + customModel.setStatus(status); + customModel.setAuthor(author); + + // Create the model as a Model Administrator + HttpResponse response = post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 201); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + if (author == null) + { + // ignore 'author' in the comparison + compareCustomModels(customModel, returnedModel, "author"); + } + else + { + compareCustomModels(customModel, returnedModel); + } + + return customModel; + } + + protected T createTypeAspect(Class glazz, String modelName, String typeAspectName, String title, String desc, + String parent) throws Exception + { + AbstractClassModel classModel = null; + String uri = "cmm/" + modelName; + if (glazz.equals(CustomType.class)) + { + classModel = new CustomType(); + uri += "/types"; + } + else + { + classModel = new CustomAspect(); + uri += "/aspects"; + } + + classModel.setName(typeAspectName); + classModel.setDescription(desc); + classModel.setTitle(title); + classModel.setParentName(parent); + + // Create type as a Model Administrator + HttpResponse response = post(uri, customModelAdmin, RestApiUtil.toJsonAsString(classModel), 201); + T returnedClassModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), glazz); + + compareCustomTypesAspects(classModel, returnedClassModel, "prefixedName"); + + return returnedClassModel; + } + + protected void compareCustomModels(CustomModel expectedModel, CustomModel actualModel, String... excludeFields) + { + boolean result = EqualsBuilder.reflectionEquals(expectedModel, actualModel, excludeFields); + assertTrue("Two models are not equal. Expected:<" + expectedModel.toString() + "> but was:<" + actualModel.toString() + ">", result); + } + + protected void compareCustomTypesAspects(AbstractClassModel expectedDetails, AbstractClassModel actualDetails, String... excludeFields) + { + List expectedProps = expectedDetails.getProperties(); + List actualProps = actualDetails.getProperties(); + // Sort them + sortIfnotNull(expectedProps); + sortIfnotNull(actualProps); + + boolean propEqualResult = true; + if (expectedProps.size() == actualProps.size()) + { + for (int i = 0, size = expectedProps.size(); i < size; i++) + { + boolean equalProp = EqualsBuilder.reflectionEquals(expectedProps.get(i), actualProps.get(i), excludeFields); + if (!equalProp) + { + propEqualResult = false; + break; + } + } + } + else + { + propEqualResult = false; + } + + if (excludeFields.length > 0) + { + int size = excludeFields.length; + excludeFields = Arrays.copyOf(excludeFields, size + 1); + excludeFields[size] = "properties"; + } + boolean result = EqualsBuilder.reflectionEquals(expectedDetails, actualDetails, excludeFields); + + String typesAspects = (expectedDetails instanceof CustomAspect) ? "aspects" : "types"; + assertTrue("Two " + typesAspects + " are not equal. Expected:<" + expectedDetails.toString() + "> but was:<" + actualDetails.toString() + ">", + (result && propEqualResult)); + } + + protected void compareCustomModelConstraints(CustomModelConstraint expectedConstraint, CustomModelConstraint actualConstraint, String... excludeFields) + { + boolean result = EqualsBuilder.reflectionEquals(expectedConstraint, actualConstraint, excludeFields); + assertTrue("Two constraints are not equal. Expected:<" + expectedConstraint.toString() + "> but was:<" + actualConstraint.toString() + ">", result); + } + + protected void compareCustomModelProperties(CustomModelProperty expectedProperty, CustomModelProperty actualProperty, String... excludeFields) + { + boolean result = EqualsBuilder.reflectionEquals(expectedProperty, actualProperty, excludeFields); + assertTrue("Two constraints are not equal. Expected:<" + expectedProperty.toString() + "> but was:<" + actualProperty.toString() + ">", result); + } + + protected Pair getTestNamespaceUriPrefixPair() + { + long timeMillis = System.currentTimeMillis(); + String uri = "http://www.alfresco.org/model/testcmmnamespace" + timeMillis + "/1.0"; + String prefix = "testcmm" + timeMillis; + + return new Pair(uri, prefix); + } + + protected CustomModelDefinition getModelDefinition(final String modelName) + { + return transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public CustomModelDefinition execute() throws Throwable + { + return customModelService.getCustomModel(modelName); + } + }); + } + + protected void sortIfnotNull(List list) + { + if (list != null && list.size() > 0) + { + Collections.sort(list); + } + } + + protected boolean hasNamespaceUri(Collection namespaces, String expectedNamespaceUri) + { + for (NamespaceDefinition ns : namespaces) + { + if (ns.getUri().equals(expectedNamespaceUri)) + { + return true; + } + } + + return false; + } + + protected boolean hasNamespacePrefix(Collection namespaces, String expectedNamespacePrefix) + { + for (NamespaceDefinition ns : namespaces) + { + if (ns.getPrefix().equals(expectedNamespacePrefix)) + { + return true; + } + } + + return false; + } + + protected CustomModelProperty getProperty(List properties, String propName) + { + for (CustomModelProperty prop : properties) + { + if (prop.getName().equals(propName)) + { + return prop; + } + } + return null; + } + + protected CustomModelNamedValue buildNamedValue(String name, String simpleValue, String... listValue) + { + CustomModelNamedValue namedValue = new CustomModelNamedValue(); + namedValue.setName(name); + namedValue.setSimpleValue(simpleValue); + if (listValue.length > 0) + { + namedValue.setListValue(Arrays.asList(listValue)); + } + + return namedValue; + } + + protected String getParameterSimpleValue(List params, String paramName) + { + for (CustomModelNamedValue p : params) + { + if (p.getName().equals(paramName)) + { + return p.getSimpleValue(); + } + } + return null; + } + + protected List getParameterListValue(List params, String paramName) + { + for (CustomModelNamedValue p : params) + { + if (p.getName().equals(paramName)) + { + return p.getListValue(); + } + } + return null; + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/CMMApiTestSuite.java b/source/test-java/org/alfresco/rest/api/tests/CMMApiTestSuite.java new file mode 100644 index 0000000000..f85915b0d9 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/CMMApiTestSuite.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests; + +import junit.framework.JUnit4TestAdapter; +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * @author Jamal Kaabi-Mofrad + */ +public class CMMApiTestSuite extends TestSuite +{ + /** + * Creates the test suite + * + * @return the test suite + */ + public static Test suite() + { + TestSuite suite = new TestSuite(); + suite.addTest(new JUnit4TestAdapter(TestCustomModel.class)); + suite.addTest(new JUnit4TestAdapter(TestCustomTypeAspect.class)); + suite.addTest(new JUnit4TestAdapter(TestCustomProperty.class)); + suite.addTest(new JUnit4TestAdapter(TestCustomConstraint.class)); + suite.addTest(new JUnit4TestAdapter(TestCustomModelExport.class)); + + return suite; + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/TestCustomConstraint.java b/source/test-java/org/alfresco/rest/api/tests/TestCustomConstraint.java new file mode 100644 index 0000000000..12c809f396 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/TestCustomConstraint.java @@ -0,0 +1,1009 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.dictionary.constraint.AbstractConstraint; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModelConstraint; +import org.alfresco.rest.api.model.CustomModelNamedValue; +import org.alfresco.rest.api.model.CustomModelProperty; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.tests.RepoService.SiteInformation; +import org.alfresco.rest.api.tests.RepoService.TestNetwork; +import org.alfresco.rest.api.tests.RepoService.TestPerson; +import org.alfresco.rest.api.tests.RepoService.TestSite; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import org.alfresco.service.cmr.dictionary.ConstraintException; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.junit.Test; + +/** + * Tests the REST API of the constraints of the {@link CustomModelService}. + * + * @author Jamal Kaabi-Mofrad + */ +public class TestCustomConstraint extends BaseCustomModelApiTest +{ + + @Test + public void testCreateConstraints() throws Exception + { + final Paging paging = getPaging(0, Integer.MAX_VALUE); + + String modelName = "testModelConstraint" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Create RegEx constraint + { + String regExConstraintName = "testFileNameRegEx" + System.currentTimeMillis(); + CustomModelConstraint regExConstraint = new CustomModelConstraint(); + regExConstraint.setName(regExConstraintName); + regExConstraint.setType("REGEX"); + regExConstraint.setTitle("test RegEx title"); + regExConstraint.setDescription("test RegEx desc"); + // Create the RegEx constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("expression", "(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$)")); + parameters.add(buildNamedValue("requiresMatch", "false")); + // Add the parameters into the constraint + regExConstraint.setParameters(parameters); + + // Try to create constraint as a non Admin user + post("cmm/" + modelName + "/constraints", nonAdminUserName, RestApiUtil.toJsonAsString(regExConstraint), 403); + + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(regExConstraint), 201); + + // Retrieve the created RegEx constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, regExConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + compareCustomModelConstraints(regExConstraint, returnedConstraint, "prefixedName"); + + // Try to create a duplicate constraint + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(regExConstraint), 409); + + // Retrieve all the model's constraints + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(1, constraints.size()); + } + + // Try to create invalid RegEx constraint + { + String regExConstraintName = "testFileNameInvalidRegEx" + System.currentTimeMillis(); + CustomModelConstraint regExConstraint = new CustomModelConstraint(); + regExConstraint.setName(regExConstraintName); + regExConstraint.setType("REGEX"); + // Create the RegEx constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("expression", "*******")); + parameters.add(buildNamedValue("requiresMatch", "false")); + // Add the parameters into the constraint + regExConstraint.setParameters(parameters); + + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(regExConstraint), 400); + } + + // Create MINMAX constraint + { + String minMaxConstraintName = "testMinMaxConstraint" + System.currentTimeMillis(); + CustomModelConstraint minMaxConstraint = new CustomModelConstraint(); + minMaxConstraint.setName(minMaxConstraintName); + minMaxConstraint.setTitle("test MinMax title"); + minMaxConstraint.setDescription("test MinMax desc"); + // Create the MinMax constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "100.0")); + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(minMaxConstraint), 400); // constraint's type is mandatory + + minMaxConstraint.setType("MINMAX"); + parameters.clear(); + parameters.add(buildNamedValue("maxValue", "abc")); // invalid number + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(minMaxConstraint), 400); + + parameters.clear(); + parameters.add(buildNamedValue("maxValue", "100")); + parameters.add(buildNamedValue("minValue", "text")); // invalid number + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(minMaxConstraint), 400); + + parameters.clear(); + parameters.add(buildNamedValue("maxValue", "100.0")); + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(minMaxConstraint), 201); + + // Retrieve the created MINMAX constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, minMaxConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + compareCustomModelConstraints(minMaxConstraint, returnedConstraint, "prefixedName"); + + // Retrieve all the model's constraints + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(2, constraints.size()); + } + + // Create LENGTH constraint + { + String lengthConstraintName = "testLengthConstraint" + System.currentTimeMillis(); + CustomModelConstraint lengthConstraint = new CustomModelConstraint(); + lengthConstraint.setName(lengthConstraintName); + lengthConstraint.setType("LENGTH"); + lengthConstraint.setTitle("test Length title"); + lengthConstraint.setDescription("test Length desc"); + // Create the Length constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxLength", "text")); // invalid number + parameters.add(buildNamedValue("minLength", "0")); + // Add the parameters into the constraint + lengthConstraint.setParameters(parameters); + + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(lengthConstraint), 400); + + parameters.clear(); + parameters.add(buildNamedValue("maxLength", "256")); + parameters.add(buildNamedValue("minLength", "1.0")); // double number + // Add the parameters into the constraint + lengthConstraint.setParameters(parameters); + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(lengthConstraint), 400); + + parameters.clear(); + parameters.add(buildNamedValue("maxLength", "256")); + parameters.add(buildNamedValue("minLength", "0")); + // Add the parameters into the constraint + lengthConstraint.setParameters(parameters); + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(lengthConstraint), 201); + + // Retrieve the created LENGTH constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, lengthConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + compareCustomModelConstraints(lengthConstraint, returnedConstraint, "prefixedName"); + + // Retrieve all the model's constraints + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(3, constraints.size()); + } + + // Create LIST constraint + { + String listConstraintName = "testListConstraint" + System.currentTimeMillis(); + CustomModelConstraint listConstraint = new CustomModelConstraint(); + listConstraint.setName(listConstraintName); + listConstraint.setType("LIST"); + listConstraint.setTitle("test List title"); + listConstraint.setDescription("test List desc"); + // Create the List constraint's parameters + List parameters = new ArrayList<>(3); + parameters.add(buildNamedValue("allowedValues", null, "High", "Normal", "Low"));// list value + parameters.add(buildNamedValue("sorted", "false")); + // Add the parameters into the constraint + listConstraint.setParameters(parameters); + + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(listConstraint), 201); + + // Retrieve the created List constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, listConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + compareCustomModelConstraints(listConstraint, returnedConstraint, "prefixedName", "parameters"); + String sorted = getParameterSimpleValue(returnedConstraint.getParameters(), "sorted"); + assertEquals("false", sorted); + List listValues = getParameterListValue(returnedConstraint.getParameters(), "allowedValues"); + assertNotNull(listValues); + assertEquals(3, listValues.size()); + assertEquals("High", listValues.get(0)); + assertEquals("Normal", listValues.get(1)); + assertEquals("Low", listValues.get(2)); + + // Retrieve all the model's constraints + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(4, constraints.size()); + } + + // Create authorityName constraint + { + String authorityNameConstraintName = "authorityNameConstraint" + System.currentTimeMillis(); + CustomModelConstraint authorityNameConstraint = new CustomModelConstraint(); + authorityNameConstraint.setName(authorityNameConstraintName); + authorityNameConstraint.setType("org.alfresco.repo.dictionary.constraint.AuthorityNameConstraint"); + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(authorityNameConstraint), 201); + + // Retrieve the created authorityName constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, authorityNameConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + compareCustomModelConstraints(authorityNameConstraint, returnedConstraint, "prefixedName"); + + // Retrieve all the model's constraints + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(5, constraints.size()); + } + + // Create Invalid constraint + { + String invalidConstraintName = "testInvalidConstraint" + System.currentTimeMillis(); + CustomModelConstraint invalidConstraint = new CustomModelConstraint(); + invalidConstraint.setName(invalidConstraintName); + invalidConstraint.setType("InvalidConstraintType"+ System.currentTimeMillis()); + invalidConstraint.setTitle("test Invalid title"); + invalidConstraint.setDescription("test Invalid desc"); + // Create the MinMax constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "100.0")); + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + invalidConstraint.setParameters(parameters); + + // Try to create an invalid constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(invalidConstraint), 400); + + // Retrieve all the model's constraints + HttpResponse response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(5, constraints.size()); + } + + // Activate the model + CustomModel updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + // Retrieve all the model's constraints + HttpResponse response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(5, constraints.size()); + + // Deactivate the model + updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + // Retrieve all the model's constraints + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(5, constraints.size()); + } + + @Test + public void testCreateConstraintAndAddToProperty() throws Exception + { + String modelName = "testModelConstraint" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Create RegEx constraint + String regExConstraintName = "testFileNameRegEx" + System.currentTimeMillis(); + CustomModelConstraint regExConstraint = new CustomModelConstraint(); + regExConstraint.setName(regExConstraintName); + regExConstraint.setType("REGEX"); + regExConstraint.setTitle("test RegEx title"); + regExConstraint.setDescription("test RegEx desc"); + // Create the RegEx constraint's parameters + List parameters= new ArrayList<>(2); + parameters.add(buildNamedValue("expression", "(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$)")); + parameters.add(buildNamedValue("requiresMatch", "false")); + // Add the parameters into the constraint + regExConstraint.setParameters(parameters); + + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(regExConstraint), 201); + + // Retrieve the created constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, regExConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + + // Retrieve all the model's constraints + Paging paging = getPaging(0, Integer.MAX_VALUE); + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(1, constraints.size()); + + // Create aspect + String aspectName = "testAspect1" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect payload = new CustomAspect(); + payload.setName(aspectName); + final String aspectPropName = "testAspect1Prop1" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property title"); + aspectProp.setDataType("d:text"); + aspectProp.setConstraintRefs(Arrays.asList(returnedConstraint.getPrefixedName()));// Add the constraint ref + List props = new ArrayList<>(1); + props.add(aspectProp); + payload.setProperties(props); + + // Create the property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 200); + + // Activate the model + CustomModel updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + // Retrieve all the model's constraints + // Test to see if the API took care of duplicate constraints when referencing a constraint within a property. + response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals(1, constraints.size()); + + // Test RegEx constrain enforcement + { + final NodeService nodeService = repoService.getNodeService(); + final QName aspectQName = QName.createQName("{" + namespacePair.getFirst() + "}" + aspectName); + + TestNetwork testNetwork = getTestFixture().getRandomNetwork(); + TestPerson person = testNetwork.createUser(); + final String siteName = "site" + System.currentTimeMillis(); + + TenantUtil.runAsUserTenant(new TenantRunAsWork() + { + @Override + public Void doWork() throws Exception + { + SiteInformation siteInfo = new SiteInformation(siteName, siteName, siteName, SiteVisibility.PRIVATE); + TestSite site = repoService.createSite(null, siteInfo); + + NodeRef nodeRef = repoService.createDocument(site.getContainerNodeRef("documentLibrary"), "Test Doc", "Test Content"); + + nodeService.addAspect(nodeRef, aspectQName, null); + assertTrue(nodeService.hasAspect(nodeRef, aspectQName)); + + try + { + QName propQName = QName.createQName("{" + namespacePair.getFirst() + "}" + aspectPropName); + nodeService.setProperty(nodeRef, propQName, "Invalid$Char."); + fail("Invalid property value. Should have caused integrity violations."); + } + catch (Exception e) + { + // Expected + } + + // Permanently remove model from repository + nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); + nodeService.deleteNode(nodeRef); + + return null; + } + }, person.getId(), testNetwork.getId()); + } + + // Deactivate the model + updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + // Test update the namespace prefix (test to see if the API updates the constraints refs with this new prefix) + CustomModel updateModelPayload = new CustomModel(); + String modifiedPrefix = namespacePair.getSecond() + "Modified"; + updateModelPayload.setNamespacePrefix(modifiedPrefix); + updateModelPayload.setNamespaceUri(namespacePair.getFirst()); + response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updateModelPayload), null, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(modifiedPrefix, returnedModel.getNamespacePrefix()); + assertEquals("The namespace URI shouldn't have changed.", namespacePair.getFirst(), returnedModel.getNamespaceUri()); + + // Test update the namespace URI + updateModelPayload = new CustomModel(); + updateModelPayload.setNamespacePrefix(modifiedPrefix); + String modifiedURI = namespacePair.getFirst() + "Modified"; + updateModelPayload.setNamespaceUri(modifiedURI); + response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updateModelPayload), null, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(modifiedURI, returnedModel.getNamespaceUri()); + assertEquals("The namespace prefix shouldn't have changed.", modifiedPrefix, returnedModel.getNamespacePrefix()); + } + + @Test + public void testCreateInlineConstraint() throws Exception + { + String modelName = "testModelInlineConstraint" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + String regExConstraintName = "testInlineFileNameRegEx" + System.currentTimeMillis(); + { + // Create RegEx constraint + CustomModelConstraint inlineRegExConstraint = new CustomModelConstraint(); + inlineRegExConstraint.setName(regExConstraintName); + inlineRegExConstraint.setType("REGEX"); + // Create the inline RegEx constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("expression", "(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$)")); + parameters.add(buildNamedValue("requiresMatch", "false")); + // Add the parameters into the constraint + inlineRegExConstraint.setParameters(parameters); + + // Create aspect + String aspectName = "testAspect1" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop1" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property title"); + aspectProp.setDataType("d:text"); + aspectProp.setConstraints(Arrays.asList(inlineRegExConstraint));// Add inline constraint + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Create the property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 200); + + // Retrieve all the model's constraints + Paging paging = getPaging(0, Integer.MAX_VALUE); + HttpResponse response = getAll("cmm/" + modelName + "/constraints", customModelAdmin, paging, 200); + List constraints = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModelConstraint.class); + assertEquals("Inline constraints should not be included with the model defined constraints.", 0, constraints.size()); + + // Retrieve the updated aspect + response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + + // Check the aspect's added property + assertEquals(1, returnedAspect.getProperties().size()); + CustomModelProperty customModelProperty = returnedAspect.getProperties().get(0); + assertEquals(aspectPropName, customModelProperty.getName()); + + assertEquals(0, customModelProperty.getConstraintRefs().size()); + List inlineConstraints = customModelProperty.getConstraints(); + assertEquals(1, inlineConstraints.size()); + compareCustomModelConstraints(inlineRegExConstraint, inlineConstraints.get(0), "prefixedName"); + } + + // Create inline and referenced constraint + { + // Create RegEx constraint + CustomModelConstraint regExConstraint = new CustomModelConstraint(); + regExConstraint.setName(regExConstraintName); // duplicate name + regExConstraint.setType("REGEX"); + regExConstraint.setTitle("test RegEx title"); + // Create the RegEx constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("expression", "(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$)")); + parameters.add(buildNamedValue("requiresMatch", "false")); + // Add the parameters into the constraint + regExConstraint.setParameters(parameters); + + // Try to create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(regExConstraint), 409); // duplicate name + + String newRegExConstraintName = "testFileNameRegEx" + System.currentTimeMillis(); + regExConstraint.setName(newRegExConstraintName); + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(regExConstraint), 201); + // Retrieve the created RegEx constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, newRegExConstraintName, 200); + CustomModelConstraint returnedRegExConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + + // Create inline anonymous LENGTH constraint + CustomModelConstraint inlineAnonymousLengthConstraint = new CustomModelConstraint(); + inlineAnonymousLengthConstraint.setType("LENGTH"); + inlineAnonymousLengthConstraint.setTitle("test Length title"); + // Create the Length constraint's parameters + parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxLength", "256")); + parameters.add(buildNamedValue("minLength", "0")); + // Add the parameters into the constraint + inlineAnonymousLengthConstraint.setParameters(parameters); + + // Create type + String typeName = "testType1" + System.currentTimeMillis(); + CustomType type = createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", "test type1 Desc", "cm:content"); + + // Update the Type by adding property + CustomType typePayload = new CustomType(); + typePayload.setName(typeName); + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property title"); + typeProp.setDataType("d:int"); + typeProp.setConstraintRefs(Arrays.asList(returnedRegExConstraint.getPrefixedName())); // Constraint Ref + typeProp.setConstraints(Arrays.asList(inlineAnonymousLengthConstraint)); // inline constraint + List props = new ArrayList<>(1); + props.add(typeProp); + typePayload.setProperties(props); + + // Try to create the property - LENGTH constraint can only be used with textual data type + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 400); + + typeProp.setDataType("d:double"); + // CTry to create the property - LENGTH constraint can only be used with textual data type + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 400); + + typeProp.setDataType("d:text"); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 200); + + // Retrieve the updated type + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName(), 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + + // Check the type's added property + assertEquals(1, returnedType.getProperties().size()); + CustomModelProperty customModelProperty = returnedType.getProperties().get(0); + assertEquals(typePropName, customModelProperty.getName()); + + assertEquals(1, customModelProperty.getConstraintRefs().size()); + assertEquals(returnedRegExConstraint.getPrefixedName(), customModelProperty.getConstraintRefs().get(0)); + + assertEquals(1, customModelProperty.getConstraints().size()); + assertNotNull(customModelProperty.getConstraints().get(0).getName()); // M2PropertyDefinition will add a name + compareCustomModelConstraints(inlineAnonymousLengthConstraint, customModelProperty.getConstraints().get(0), "prefixedName", "name"); + } + } + + @Test + public void testCreateListConstraintInvalid() throws Exception + { + String modelName = "testModelConstraintInvalid" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Create aspect + String aspectName = "testAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property title"); + aspectProp.setDataType("d:int"); + + //Create LIST constraint + String inlineListConstraintName = "testListConstraint" + System.currentTimeMillis(); + CustomModelConstraint inlineListConstraint = new CustomModelConstraint(); + inlineListConstraint.setName(inlineListConstraintName); + inlineListConstraint.setType("LIST"); + inlineListConstraint.setTitle("test List title"); + inlineListConstraint.setDescription("test List desc"); + // Create the List constraint's parameters + List parameters = new ArrayList<>(3); + parameters.add(buildNamedValue("allowedValues", null, "a", "b", "c"));// text list value, but the the property data type is d:int + parameters.add(buildNamedValue("sorted", "false")); + // Add the parameters into the constraint + inlineListConstraint.setParameters(parameters); + aspectProp.setConstraints(Arrays.asList(inlineListConstraint));// Add inline constraint + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create the property - Invalid LIST values + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + // Test d:double LIST values with d:int property data type + parameters = new ArrayList<>(3); + parameters.add(buildNamedValue("allowedValues", null, "1.0", "2.0", "3.0"));// double list value, but the the property data type is d:int + parameters.add(buildNamedValue("sorted", "false")); + // Add the parameters into the constraint + inlineListConstraint.setParameters(parameters); + aspectProp.setConstraints(Arrays.asList(inlineListConstraint));// Add inline constraint + props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create the property - Invalid LIST values + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + } + + @Test + public void testCreateMinMaxConstraintInvalid() throws Exception + { + String modelName = "testModelMinMaxInvalid" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Create aspect + String aspectName = "testAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property title"); + aspectProp.setDataType("d:text"); + + String minMaxConstraintName = "testMinMaxConstraint" + System.currentTimeMillis(); + CustomModelConstraint minMaxConstraint = new CustomModelConstraint(); + minMaxConstraint.setType("MINMAX"); + minMaxConstraint.setName(minMaxConstraintName); + minMaxConstraint.setTitle("test MinMax title"); + minMaxConstraint.setDescription("test MinMax desc"); + // Create the MinMax constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "100.0")); + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + + aspectProp.setConstraints(Arrays.asList(minMaxConstraint));// Add inline constraint + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create constraint as a Model Administrator + // MINMAX constraint can only be used with numeric data type. + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + // Change type + aspectProp.setDataType("d:datetime"); + // MINMAX constraint can only be used with numeric data type. + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + // SHA-1126 + { + //Change type + aspectProp.setDataType("d:double"); + parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "0.0")); + parameters.add(buildNamedValue("minValue", "-5.0")); + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + + aspectProp.setConstraints(Arrays.asList(minMaxConstraint));// Add inline constraint + props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + // Maximum value of the MINMAX constraint must be a positive nonzero value. + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + } + } + + @Test + public void testPropDefaultValueWithInlineConstraint() throws Exception + { + String modelName = "testModelInlineConstraint" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + { + // Create RegEx constraint + String regExConstraintName = "testInlineFileNameRegEx" + System.currentTimeMillis(); + CustomModelConstraint inlineRegExConstraint = new CustomModelConstraint(); + inlineRegExConstraint.setName(regExConstraintName); + inlineRegExConstraint.setType("REGEX"); + // Create the inline RegEx constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("expression", "(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$)")); + parameters.add(buildNamedValue("requiresMatch", "false")); + // Add the parameters into the constraint + inlineRegExConstraint.setParameters(parameters); + + // Create aspect + String aspectName = "testAspect1" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop1" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property with REGEX constraint"); + aspectProp.setDataType("d:text"); + aspectProp.setDefaultValue("invalid props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 409); + } + + { + // Create inline anonymous LENGTH constraint + CustomModelConstraint inlineAnonymousLengthConstraint = new CustomModelConstraint(); + inlineAnonymousLengthConstraint.setType("LENGTH"); + // Create the Length constraint's parameters + Listparameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxLength", "4")); + parameters.add(buildNamedValue("minLength", "0")); + // Add the parameters into the constraint + inlineAnonymousLengthConstraint.setParameters(parameters); + + // Create type + String typeName = "testType1" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", "test type1 Desc", "cm:content"); + + // Update the Type by adding property + CustomType typePayload = new CustomType(); + typePayload.setName(typeName); + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property with LENGTH constraint"); + typeProp.setDataType("d:text"); + typeProp.setDefaultValue("abcdef"); // Invalid length + typeProp.setConstraints(Arrays.asList(inlineAnonymousLengthConstraint)); // inline constraint + List props = new ArrayList<>(1); + props.add(typeProp); + typePayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 409); + } + + { + // Create inline anonymous MINMAX constraint + CustomModelConstraint inlineAnonymousMinMaxConstraint = new CustomModelConstraint(); + inlineAnonymousMinMaxConstraint.setType("MINMAX"); + // Create the MinMax constraint's parameters + Listparameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "10")); + parameters.add(buildNamedValue("minValue", "0")); + // Add the parameters into the constraint + inlineAnonymousMinMaxConstraint.setParameters(parameters); + + // Create type + String typeName = "testType1" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", "test type1 Desc", "cm:content"); + + // Update the Type by adding property + CustomType typePayload = new CustomType(); + typePayload.setName(typeName); + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property with MINMAX constraint"); + typeProp.setDataType("d:int"); + typeProp.setDefaultValue("20"); // Not in the defined range [0,10] + typeProp.setConstraints(Arrays.asList(inlineAnonymousMinMaxConstraint)); // inline constraint + List props = new ArrayList<>(1); + props.add(typeProp); + typePayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 409); + } + + { + // Create LIST constraint + String listConstraintName = "testListConstraint" + System.currentTimeMillis(); + CustomModelConstraint inlineListConstraint = new CustomModelConstraint(); + inlineListConstraint.setName(listConstraintName); + inlineListConstraint.setType("LIST"); + // Create the List constraint's parameters + List parameters = new ArrayList<>(3); + parameters.add(buildNamedValue("allowedValues", null, "one", "two", "three")); + parameters.add(buildNamedValue("sorted", "false")); + // Add the parameters into the constraint + inlineListConstraint.setParameters(parameters); + + // Create aspect + String aspectName = "testAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property with LIST constraint"); + aspectProp.setDataType("d:text"); + aspectProp.setDefaultValue("four"); // Not in the list + aspectProp.setConstraints(Arrays.asList(inlineListConstraint));// Add inline constraint + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 409); + } + + { + // Create Java Class constraint + String inlineJavaClassConstraintName = "testJavaClassConstraint" + System.currentTimeMillis(); + CustomModelConstraint inlineListConstraint = new CustomModelConstraint(); + inlineListConstraint.setName(inlineJavaClassConstraintName); + inlineListConstraint.setType("org.alfresco.rest.api.tests.TestCustomConstraint$DummyJavaClassConstraint"); + + // Create aspect + String aspectName = "testAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property with Java Class constraint"); + aspectProp.setDataType("d:text"); + aspectProp.setDefaultValue("invalid#value"); // Invalid default value + aspectProp.setConstraints(Arrays.asList(inlineListConstraint));// Add inline constraint + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 409); + } + } + + @Test + public void testPropDefaultValueWithConstraintRef() throws Exception + { + String modelName = "testModelConstraintRef" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + { + // Create List constraint + String listConstraintName = "testListConstraint" + System.currentTimeMillis(); + CustomModelConstraint listConstraint = new CustomModelConstraint(); + listConstraint.setName(listConstraintName); + listConstraint.setType("LIST"); + // Create the List constraint's parameters + List parameters = new ArrayList<>(3); + parameters.add(buildNamedValue("allowedValues", null, "London", "Paris", "New York"));// list value + parameters.add(buildNamedValue("sorted", "false")); + // Add the parameters into the constraint + listConstraint.setParameters(parameters); + + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(listConstraint), 201); + // Retrieve the created List constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, listConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + + // Create aspect + String aspectName = "testAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + final String aspectPropName = "testAspect1Prop" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property with LIST constraint ref"); + aspectProp.setDataType("d:text"); + aspectProp.setDefaultValue("Berlin"); // Not in the list + aspectProp.setConstraintRefs(Arrays.asList(returnedConstraint.getPrefixedName())); // constrain ref + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 409); + } + + { + // Create MINMAX constraint + String minMaxConstraintName = "testMinMaxConstraint" + System.currentTimeMillis(); + CustomModelConstraint minMaxConstraint = new CustomModelConstraint(); + minMaxConstraint.setName(minMaxConstraintName); + minMaxConstraint.setType("MINMAX"); + // Create the MinMax constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "100")); + parameters.add(buildNamedValue("minValue", "50")); + // Add the parameters into the constraint + minMaxConstraint.setParameters(parameters); + + // Create constraint as a Model Administrator + post("cmm/" + modelName + "/constraints", customModelAdmin, RestApiUtil.toJsonAsString(minMaxConstraint), 201); + // Retrieve the created MinMax constraint + HttpResponse response = getSingle("cmm/" + modelName + "/constraints", customModelAdmin, minMaxConstraintName, 200); + CustomModelConstraint returnedConstraint = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelConstraint.class); + + // Create type + String typeName = "testType1" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", "test type1 Desc", "cm:content"); + + // Update the Type by adding property + CustomType typePayload = new CustomType(); + typePayload.setName(typeName); + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property with MINMAX constraint ref"); + typeProp.setDataType("d:int"); + typeProp.setDefaultValue("35"); // Not in the defined range [50,100] + typeProp.setConstraintRefs(Arrays.asList(returnedConstraint.getPrefixedName())); // constrain ref + List props = new ArrayList<>(1); + props.add(typeProp); + typePayload.setProperties(props); + + // Try to create the property - constraint violation + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 409); + } + } + + public static class DummyJavaClassConstraint extends AbstractConstraint + { + @Override + protected void evaluateSingleValue(Object value) + { + String checkValue = DefaultTypeConverter.INSTANCE.convert(String.class, value); + + if (checkValue.contains("#")) + { + throw new ConstraintException("The value must not contain '#'"); + } + } + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/TestCustomModel.java b/source/test-java/org/alfresco/rest/api/tests/TestCustomModel.java new file mode 100644 index 0000000000..4061bef4c4 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/TestCustomModel.java @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.junit.Test; + +/** + * Tests the REST API of the models of the {@link CustomModelService}. + * + * @author Jamal Kaabi-Mofrad + */ +public class TestCustomModel extends BaseCustomModelApiTest +{ + + @Test + public void testCreateBasicModel() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + + CustomModel customModel = new CustomModel(); + customModel.setName(modelName); + customModel.setNamespaceUri(namespacePair.getFirst()); + customModel.setNamespacePrefix(namespacePair.getSecond()); + customModel.setDescription("Test model description"); + customModel.setStatus(CustomModel.ModelStatus.DRAFT); + + // Try to create the model as a non Admin user + post("cmm", nonAdminUserName, RestApiUtil.toJsonAsString(customModel), 403); + + // Create the model as a Model Administrator + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 201); + + // Retrieve the created model + HttpResponse response = getSingle("cmm", customModelAdmin, modelName, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + // Check the retrieved model is the expected model. + // Note: since we didn't specify the Author when created the Model, + // we have to exclude it from the objects comparison. Because, + // the system will add the current authenticated user as the author + // of the model, if the Author hasn't been set. + compareCustomModels(customModel, returnedModel, "author"); + } + + @Test + public void testCreateBasicModel_Invalid() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + + CustomModel customModel = new CustomModel(); + customModel.setName(modelName); + customModel.setNamespaceUri(namespacePair.getFirst()); + customModel.setNamespacePrefix(namespacePair.getSecond()); + + // Test invalid inputs + { + customModel.setName(modelName + ""); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); + + customModel.setName("prefix:" + modelName); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // Invalid name. Contains ':' + + customModel.setName("prefix " + modelName); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // Invalid name. Contains space + + customModel.setName(modelName); + customModel.setNamespacePrefix(namespacePair.getSecond()+" space"); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // Invalid prefix. Contains space + + customModel.setNamespacePrefix(namespacePair.getSecond()+"invalid/"); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // Invalid prefix. Contains '/' + + customModel.setNamespacePrefix(namespacePair.getSecond()); + customModel.setNamespaceUri(namespacePair.getFirst()+" space"); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // Invalid URI. Contains space + + customModel.setNamespaceUri(namespacePair.getFirst()+"\\"); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // Invalid URI. Contains '\' + } + + // Test mandatory properties of the model + { + customModel.setName(""); + customModel.setNamespacePrefix(namespacePair.getSecond()); + customModel.setNamespaceUri(namespacePair.getFirst()); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // name is mandatory + + customModel.setName(modelName); + customModel.setNamespaceUri(null); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // namespaceUri is mandatory + + customModel.setName(modelName); + customModel.setNamespaceUri(namespacePair.getFirst()); + customModel.setNamespacePrefix(null); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 400); // namespacePrefix is mandatory + } + + // Test duplicate model name + { + // Test create a model with the same name as the bootstrapped model + customModel.setName("contentmodel"); + customModel.setNamespaceUri(namespacePair.getFirst()); + customModel.setNamespacePrefix(namespacePair.getSecond()); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 409); + + // Create the model + customModel.setName(modelName); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 201); + + // Create a duplicate model + // Set a new namespace to make sure the 409 status code is returned + // because of a name conflict rather than namespace URI + namespacePair = getTestNamespaceUriPrefixPair(); + customModel.setNamespaceUri(namespacePair.getFirst()); + customModel.setNamespacePrefix(namespacePair.getSecond()); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 409); + } + + // Test duplicate namespaceUri + { + String modelNameTwo = "testModelTwo" + System.currentTimeMillis(); + Pair namespacePairTwo = getTestNamespaceUriPrefixPair(); + + CustomModel customModelTwo = new CustomModel(); + customModelTwo.setName(modelNameTwo); + customModelTwo.setNamespaceUri(namespacePairTwo.getFirst()); + customModelTwo.setNamespacePrefix(namespacePairTwo.getSecond()); + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModelTwo), 201); + + String modelNameThree = "testModelThree" + System.currentTimeMillis(); + Pair namespacePairThree = getTestNamespaceUriPrefixPair(); + CustomModel customModelThree = new CustomModel(); + customModelThree.setName(modelNameThree); + customModelThree.setNamespaceUri(namespacePairTwo.getFirst()); // duplicate URI + customModelThree.setNamespacePrefix(namespacePairThree.getSecond()); + + // Try to create a model with a namespace uri which has already been used. + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModelThree), 409); + + customModelThree.setNamespaceUri(namespacePairThree.getFirst()); + customModelThree.setNamespacePrefix(namespacePairTwo.getSecond()); // duplicate prefix + + // Try to create a model with a namespace prefix which has already been used. + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModelThree), 409); + } + } + + @Test + public void testListBasicModels() throws Exception + { + String modelName_1 = "testModel1" + System.currentTimeMillis(); + // Create the model as a Model Administrator + CustomModel customModel_1 = createCustomModel(modelName_1, getTestNamespaceUriPrefixPair(), ModelStatus.DRAFT); + + String modelName_2 = "testModel2" + System.currentTimeMillis(); + CustomModel customModel_2 = createCustomModel(modelName_2, getTestNamespaceUriPrefixPair(), ModelStatus.DRAFT); + + String modelName_3 = "testModel3" + System.currentTimeMillis(); + CustomModel customModel_3 = createCustomModel(modelName_3, getTestNamespaceUriPrefixPair(), ModelStatus.DRAFT); + + Paging paging = getPaging(0, Integer.MAX_VALUE); + HttpResponse response = getAll("cmm", customModelAdmin, paging, 200); + List models = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomModel.class); + + assertTrue(models.size() >= 3); + assertTrue(models.contains(customModel_1)); + assertTrue(models.contains(customModel_2)); + assertTrue(models.contains(customModel_3)); + } + + @Test + public void testActivateCustomModel() throws Exception + { + String modelNameOne = "testActivateModelOne" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + CustomModel customModelOne = createCustomModel(modelNameOne, namespacePair, ModelStatus.DRAFT, "Test model description", "Jane Doe"); + + // Retrieve the created model and check its status (the default is DRAFT) + HttpResponse response = getSingle("cmm", customModelAdmin, modelNameOne, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.DRAFT, returnedModel.getStatus()); + + // We only want to update the status, so ignore the other properties + CustomModel updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.ACTIVE); + + // Try to activate the model as a non Admin user + put("cmm", nonAdminUserName, modelNameOne, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 403); + + // Activate the model as a Model Administrator + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + response = getSingle("cmm", customModelAdmin, modelNameOne, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.ACTIVE, returnedModel.getStatus()); + // Check other properties have not been modified + compareCustomModels(customModelOne, returnedModel, "status"); + + // Try to activate the already activated model as a Model Administrator + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 500); + + // Create another Model + String modelNameTwo = "testActivateModelTwo" + System.currentTimeMillis(); + Pair namespacePairTwo = getTestNamespaceUriPrefixPair(); + CustomModel customModelTwo = createCustomModel(modelNameTwo, namespacePairTwo, ModelStatus.DRAFT, null, "John Doe"); + + // Activate the model as a Model Administrator + customModelTwo.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameTwo, RestApiUtil.toJsonAsString(customModelTwo), SELECT_STATUS_QS, 200); + + response = getSingle("cmm", customModelAdmin, modelNameTwo, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.ACTIVE, returnedModel.getStatus()); + // Check other properties have not been modified + compareCustomModels(customModelTwo, returnedModel, "status"); + } + + @Test + public void testDeactivateCustomModel() throws Exception + { + String modelNameOne = "testDeactivateModelOne" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + CustomModel customModelOne = createCustomModel(modelNameOne, namespacePair, ModelStatus.ACTIVE, null, "Mark Moe"); + + // Retrieve the created model and check its status + HttpResponse response = getSingle("cmm", customModelAdmin, modelNameOne, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.ACTIVE, returnedModel.getStatus()); + + // We only want to update the status (Deactivate), so ignore the other properties + CustomModel updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.DRAFT); + + // Try to deactivate the model as a non Admin user + put("cmm", nonAdminUserName, modelNameOne, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 403); + + // Deactivate the model as a Model Administrator + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + response = getSingle("cmm", customModelAdmin, modelNameOne, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.DRAFT, returnedModel.getStatus()); + // Check other properties have not been modified + compareCustomModels(customModelOne, returnedModel, "status"); + + // Try to deactivate the already deactivated model as a Model Administrator + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 500); + + // Activate/Deactivate a model with an aspect + { + // Create another Model + final String modelNameTwo = "testDeactivateModelTwo" + System.currentTimeMillis(); + Pair namespacePairTwo = getTestNamespaceUriPrefixPair(); + CustomModel customModelTwo = createCustomModel(modelNameTwo, namespacePairTwo, ModelStatus.DRAFT, null, "Mark Moe"); + + // Aspect + CustomAspect aspect = new CustomAspect(); + aspect.setName("testMarkerAspect"); + post("cmm/" + modelNameTwo + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 201); + // Retrieve the created aspect + getSingle("cmm/" + modelNameTwo + "/aspects", customModelAdmin, aspect.getName(), 200); + + // Activate the model as a Model Administrator + customModelTwo.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameTwo, RestApiUtil.toJsonAsString(customModelTwo), SELECT_STATUS_QS, 200); + + response = getSingle("cmm", customModelAdmin, modelNameTwo, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.ACTIVE, returnedModel.getStatus()); + + updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.DRAFT); + // Deactivate the model as a Model Administrator + put("cmm", customModelAdmin, modelNameTwo, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + response = getSingle("cmm", customModelAdmin, modelNameTwo, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.DRAFT, returnedModel.getStatus()); + } + } + + @Test + public void testDeleteCustomModel() throws Exception + { + String modelName = "testDeleteModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + CustomModel customModel = createCustomModel(modelName, namespacePair, ModelStatus.DRAFT, null, "Joe Bloggs"); + + // Retrieve the created model + HttpResponse response = getSingle("cmm", customModelAdmin, modelName, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + compareCustomModels(customModel, returnedModel); + + // Try to delete the model as a non Admin user + delete("cmm", nonAdminUserName, modelName, 403); + + // Delete the model as a Model Administrator + delete("cmm", customModelAdmin, modelName, 204); + + // Create the model again + post("cmm", customModelAdmin, RestApiUtil.toJsonAsString(customModel), 201); + + // Activated the model + CustomModel updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + + // Try to delete the active model + delete("cmm", customModelAdmin, modelName, 409); + + // Deactivate and then delete the model + updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + delete("cmm", customModelAdmin, modelName, 204); + } + + @Test + public void testUpdateBasicModel() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT, "Test model description", null); + + //Test update name + CustomModel updatePayload = new CustomModel(); + String newName = modelName + "Modified"; + updatePayload.setName(newName); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 400); // Cannot update the model name + + // Test update the namespace URI (already in-use) + updatePayload = new CustomModel(); + updatePayload.setNamespaceUri("http://www.alfresco.org/model/content/1.0"); + updatePayload.setNamespacePrefix("newPrefix"); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 409); // The namespace uri has already been used + + // Test update the namespace Prefix (already in-use) + updatePayload = new CustomModel(); + updatePayload.setNamespaceUri(getTestNamespaceUriPrefixPair().getFirst()); + updatePayload.setNamespacePrefix("cm"); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 409); // The namespace prefix has already been used + + // Test update the namespace URI (without sending the namespace prefix) + updatePayload = new CustomModel(); + updatePayload.setNamespaceUri(getTestNamespaceUriPrefixPair().getFirst()); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 400); // The namespace prefix is mandatory + + // Test update the namespace URI only + updatePayload = new CustomModel(); + updatePayload.setNamespacePrefix( namespacePair.getSecond()); + Pair newURI = getTestNamespaceUriPrefixPair(); + updatePayload.setNamespaceUri(newURI.getFirst()); + HttpResponse response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(newURI.getFirst(), returnedModel.getNamespaceUri()); + assertEquals("The namespace prefix shouldn't have changed.", namespacePair.getSecond(), returnedModel.getNamespacePrefix()); + + // Test update the namespace prefix (without sending the namespace URI) + updatePayload = new CustomModel(); + updatePayload.setNamespacePrefix("newPrefix"); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 400); // The namespce uri is mandatory + + // Test update the namespace prefix only + updatePayload = new CustomModel(); + updatePayload.setNamespaceUri(namespacePair.getFirst()); + Pair newPrefix = getTestNamespaceUriPrefixPair(); + updatePayload.setNamespacePrefix( newPrefix.getSecond()); + response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(newPrefix.getSecond(), returnedModel.getNamespacePrefix()); + assertEquals("The namespace URI shouldn't have changed.", namespacePair.getFirst(), returnedModel.getNamespaceUri()); + + // Test a valid update + updatePayload = new CustomModel(); + Pair newNamespacePair = getTestNamespaceUriPrefixPair(); + updatePayload.setNamespaceUri(newNamespacePair.getFirst()); + updatePayload.setNamespacePrefix(newNamespacePair.getSecond()); + updatePayload.setDescription("Test model description Modified"); + updatePayload.setAuthor("John Moe"); + updatePayload.setStatus(ModelStatus.ACTIVE); // This should be ignored + + // Try to update the model as a non Admin user + put("cmm", nonAdminUserName, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 403); + // Update the model as a Model Administrator + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 200); + + // Retrieve the updated model + response = getSingle("cmm", customModelAdmin, modelName, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + compareCustomModels(updatePayload, returnedModel, "name", "status"); + assertEquals("The model status should only be updated via '?select=status' request.", ModelStatus.DRAFT, returnedModel.getStatus()); + + // Activate the model as a Model Administrator + updatePayload = new CustomModel(); + updatePayload.setStatus(ModelStatus.ACTIVE); + response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), SELECT_STATUS_QS, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.ACTIVE, returnedModel.getStatus()); + + // Try to update the ACTIVE model's namespace URI + updatePayload = new CustomModel(); + newNamespacePair = getTestNamespaceUriPrefixPair(); + updatePayload.setNamespaceUri(newNamespacePair.getFirst()); + updatePayload.setNamespacePrefix(returnedModel.getNamespacePrefix()); + // Cannot update the namespace uri and/or namespace prefix when the model is Active. + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 409); + + // Try to update the ACTIVE model's namespace Prefix + updatePayload = new CustomModel(); + updatePayload.setNamespaceUri(returnedModel.getNamespaceUri()); + updatePayload.setNamespacePrefix("myNewPrefix"); + // Cannot update the namespace uri and/or namespace prefix when the model is Active. + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 409); + + // Test a valid update of an Active model (you can only update desc and author) + updatePayload = new CustomModel(); + updatePayload.setNamespaceUri(returnedModel.getNamespaceUri()); + updatePayload.setNamespacePrefix(returnedModel.getNamespacePrefix()); + updatePayload.setDescription("Test modifying active model description"); + updatePayload.setAuthor("Mark Miller"); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 200); + + // Retrieve the updated active model + response = getSingle("cmm", customModelAdmin, modelName, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + compareCustomModels(updatePayload, returnedModel, "name", "status"); + } + + @Test + //SHA-726 + public void testUpdateModel_WithAspectsAndTypes() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Add type + String typeBaseName = "testTypeBase" + System.currentTimeMillis(); + final String typeBaseNameWithPrefix = namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typeBaseName; + createTypeAspect(CustomType.class, modelName, typeBaseName, "test typeBase title", null, "cm:content"); + + // Add aspect + final String aspectName = "testAspect" + System.currentTimeMillis(); + final String aspectNameWithPrefix = namespacePair.getSecond() + QName.NAMESPACE_PREFIX + aspectName; + createTypeAspect(CustomAspect.class, modelName, aspectName, null, null, null); + + // Activate the model + CustomModel modelOneStatusPayload = new CustomModel(); + modelOneStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 200); + + // Add another type with 'typeBaseName' as its parent + String childTypeName = "testTypeChild" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, childTypeName, "test typeChild title", "test typeChild Desc", typeBaseNameWithPrefix); + + // Add another aspect with 'aspectName' as its parent + final String childAspectName = "testChildAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, childAspectName, "test child aspect title", null, aspectNameWithPrefix); + + // Deactivate the model + modelOneStatusPayload = new CustomModel(); + modelOneStatusPayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 200); + + // Test update the namespace prefix + CustomModel updatePayload = new CustomModel(); + String modifiedPrefix = namespacePair.getSecond() + "Modified"; + updatePayload.setNamespacePrefix(modifiedPrefix); + updatePayload.setNamespaceUri(namespacePair.getFirst()); + HttpResponse response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(modifiedPrefix, returnedModel.getNamespacePrefix()); + assertEquals("The namespace URI shouldn't have changed.", namespacePair.getFirst(), returnedModel.getNamespaceUri()); + + // Test update the namespace URI + updatePayload = new CustomModel(); + updatePayload.setNamespacePrefix(modifiedPrefix); + String modifiedURI = namespacePair.getFirst() + "Modified"; + updatePayload.setNamespaceUri(modifiedURI); + response = put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(updatePayload), null, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(modifiedURI, returnedModel.getNamespaceUri()); + assertEquals("The namespace prefix shouldn't have changed.", modifiedPrefix, returnedModel.getNamespacePrefix()); + + // Retrieve the child type + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, childTypeName, 200); + CustomType returnedChildType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + final String newTypeParentName = modifiedPrefix + QName.NAMESPACE_PREFIX + typeBaseName; + assertEquals("The parent name prefix should have been updated.", newTypeParentName, returnedChildType.getParentName()); + + // Retrieve the child aspect + response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, childAspectName, 200); + CustomAspect returnedChildAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + final String newAspectParentName = modifiedPrefix + QName.NAMESPACE_PREFIX + aspectName; + assertEquals("The parent name prefix should have been updated.", newAspectParentName, returnedChildAspect.getParentName()); + } + + // SHA-808 + @Test + public void testModelsCircularDependency() throws Exception + { + // Model One + String modelNameOne = "testModelOne" + System.currentTimeMillis(); + Pair namespacePairOne = getTestNamespaceUriPrefixPair(); + // Create the modelOne as a Model Administrator + createCustomModel(modelNameOne, namespacePairOne, ModelStatus.DRAFT); + + // Add typeA_M1 into modelOne + String typeA_M1 = "testTypeA_M1" + System.currentTimeMillis(); + final String typeA_M1_WithPrefix = namespacePairOne.getSecond() + QName.NAMESPACE_PREFIX + typeA_M1; + createTypeAspect(CustomType.class, modelNameOne, typeA_M1, "test typeA_M1 title", null, "cm:content"); + + // Activate modelOne + CustomModel modelOneStatusPayload = new CustomModel(); + modelOneStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 200); + + // Add another type into modelOne with 'typeA_M1' as its parent + String typeB_M1 = "testTypeB_M1" + System.currentTimeMillis(); + final String typeB_M1_WithPrefix = namespacePairOne.getSecond() + QName.NAMESPACE_PREFIX + typeB_M1; + createTypeAspect(CustomType.class, modelNameOne, typeB_M1, "test typeB_M1 title", "test typeB_M1 Desc", typeA_M1_WithPrefix); + + // Model Two + String modelNameTwo = "testModelTwo" + System.currentTimeMillis(); + Pair namespacePairTwo = getTestNamespaceUriPrefixPair(); + // Create the modelTwo as a Model Administrator + createCustomModel(modelNameTwo, namespacePairTwo, ModelStatus.DRAFT); + + // Add type1_M2 into modelTwo with 'typeB_M1' (from modelOne) as its parent + String type1_M2 = "testType1_M2" + System.currentTimeMillis(); + final String type1_M2_WithPrefix = namespacePairTwo.getSecond() + QName.NAMESPACE_PREFIX + type1_M2; + createTypeAspect(CustomType.class, modelNameTwo, type1_M2, "test type1_M2 title", null, typeB_M1_WithPrefix ); + + // Activate modelTwo + CustomModel modelTwoStatusPayload = new CustomModel(); + modelTwoStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameTwo, RestApiUtil.toJsonAsString(modelTwoStatusPayload), SELECT_STATUS_QS, 200); + + // Test that the API can handle "circular dependency" - (modelOne depends on modelTwo) + { + // Add another type into modelOne with 'type1_M2' (from modelTwo) as its parent + String typeC_M1 = "testTypeC_M1" + System.currentTimeMillis(); + CustomType typeC_M1_Payload = new CustomType(); + typeC_M1_Payload.setName(typeC_M1); + typeC_M1_Payload.setTitle("test typeC_M1 title"); + typeC_M1_Payload.setParentName(type1_M2_WithPrefix); // => 'type1_M2' (from modelTwo) + + // Try to create typeC_M1 which has 'circular dependency' + post("cmm/" + modelNameOne + "/types", customModelAdmin, RestApiUtil.toJsonAsString(typeC_M1_Payload), 409); //Constraint violation + } + + // Model Three + String modelNameThree = "testModelThree" + System.currentTimeMillis(); + Pair namespacePairThree = getTestNamespaceUriPrefixPair(); + // Create the modelThree as a Model Administrator + createCustomModel(modelNameThree, namespacePairThree, ModelStatus.DRAFT); + + // Add type1_M3 into modelThree with 'type1_M2' (from modelTwo) as its parent + String type1_M3 = "testType1_M3" + System.currentTimeMillis(); + final String type1_M3_WithPrefix = namespacePairThree.getSecond() + QName.NAMESPACE_PREFIX + type1_M3; + createTypeAspect(CustomType.class, modelNameThree, type1_M3, "test type1_M3 title", null, type1_M2_WithPrefix ); + + // Activate modelThree + CustomModel modelThreeStatusPayload = new CustomModel(); + modelThreeStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameThree, RestApiUtil.toJsonAsString(modelThreeStatusPayload), SELECT_STATUS_QS, 200); + + // Test that the API can handle "circular dependency" - (modelOne depends on modelThree) + { + // Add another type into modelOne with 'type1_M3' (from modelThree) as its parent + String typeC_M1 = "testTypeC_M1" + System.currentTimeMillis(); + CustomType typeC_M1_Payload = new CustomType(); + typeC_M1_Payload.setName(typeC_M1); + typeC_M1_Payload.setTitle("test typeC_M1 title"); + typeC_M1_Payload.setParentName(type1_M3_WithPrefix); // => 'type1_M3' (from modelThree) + + // Try to create typeC_M1 which has 'circular dependency' + post("cmm/" + modelNameOne + "/types", customModelAdmin, RestApiUtil.toJsonAsString(typeC_M1_Payload), 409); //Constraint violation + } + + // Model Three + String modelNameFour = "testModelFour" + System.currentTimeMillis(); + Pair namespacePairFour = getTestNamespaceUriPrefixPair(); + // Create the modelFour as a Model Administrator + createCustomModel(modelNameFour, namespacePairFour, ModelStatus.DRAFT); + + // Add type1_M4 into modelFour with 'type1_M3' (from modelThree) as its parent + String type1_M4 = "testType1_M4" + System.currentTimeMillis(); + final String type1_M4_WithPrefix = namespacePairFour.getSecond() + QName.NAMESPACE_PREFIX + type1_M4; + createTypeAspect(CustomType.class, modelNameFour, type1_M4, "test type1_M4 title", null, type1_M3_WithPrefix ); + + // Activate modelFour + CustomModel modelFourStatusPayload = new CustomModel(); + modelFourStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameFour, RestApiUtil.toJsonAsString(modelFourStatusPayload), SELECT_STATUS_QS, 200); + + // Test that the API can handle "circular dependency" - (modelOne depends on modelFour) + { + // Add another type into modelOne with 'type1_M4' (from modelFour) as its parent + String typeC_M1 = "testTypeC_M1" + System.currentTimeMillis(); + CustomType typeC_M1_Payload = new CustomType(); + typeC_M1_Payload.setName(typeC_M1); + typeC_M1_Payload.setTitle("test typeC_M1 title"); + typeC_M1_Payload.setParentName(type1_M4_WithPrefix); // => 'type1_M4' (from modelFour) + + // Try to create typeC_M1 which has 'circular dependency' + post("cmm/" + modelNameOne + "/types", customModelAdmin, RestApiUtil.toJsonAsString(typeC_M1_Payload), 409); //Constraint violation + } + + // Test that the API can handle "circular dependency" - (modelTwo depends on modelFour) + { + // Add another type into modelTwo with 'type1_M4' (from modelFour) as its parent + String type2_M2 = "testType2_M2" + System.currentTimeMillis(); + CustomType type2_M2_Payload = new CustomType(); + type2_M2_Payload.setName(type2_M2); + type2_M2_Payload.setTitle("test type2_M2 title"); + type2_M2_Payload.setParentName(type1_M4_WithPrefix); // => 'type1_M4' (from modelFour) + + // Try to create type2_M2 which has 'circular dependency' + post("cmm/" + modelNameTwo + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type2_M2_Payload), 409); //Constraint violation + } + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/TestCustomModelExport.java b/source/test-java/org/alfresco/rest/api/tests/TestCustomModelExport.java new file mode 100644 index 0000000000..a193c5ecb0 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/TestCustomModelExport.java @@ -0,0 +1,137 @@ +package org.alfresco.rest.api.tests; +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Set; + +import org.alfresco.repo.dictionary.CMMDownloadTestUtil; +import org.alfresco.rest.api.model.CustomModelDownload; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.download.DownloadStatus; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; +import org.junit.Test; + +/** + * Tests REST API download of the {@link CustomModelService}. + * + * @author Jamal Kaabi-Mofrad + */ +public class TestCustomModelExport extends BaseCustomModelApiTest +{ + + private static final long PAUSE_TIME = 1000; + + private CMMDownloadTestUtil downloadTestUtil; + + @Override + public void setup() throws Exception + { + super.setup(); + this.downloadTestUtil = new CMMDownloadTestUtil(applicationContext); + } + + @Override + public void tearDown() throws Exception + { + this.downloadTestUtil.cleanup(); + super.tearDown(); + } + + @Test + public void testCreateDownload() throws Exception + { + final String modelName = "testModel" + System.currentTimeMillis(); + final String modelExportFileName = modelName + ".xml"; + final String shareExtExportFileName = "CMM_" + modelName + "_module.xml"; + + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT, null, "Mark Moe"); + + // Add type + String typeBaseName = "testTypeBase" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeBaseName, "test typeBase title", "test typeBase Desc", "cm:content"); + + // Create Share extension module + downloadTestUtil.createShareExtModule(modelName); + + // Try to create download the model as a non Admin user + post("cmm/" + modelName + "/download", nonAdminUserName, RestApiUtil.toJsonAsString(new CustomModelDownload()), getExtModuleQS(false), 403); + + // Create download for custom model only + HttpResponse response = post("cmm/" + modelName + "/download", customModelAdmin, RestApiUtil.toJsonAsString(new CustomModelDownload()), getExtModuleQS(false), 201); + CustomModelDownload returnedDownload = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelDownload.class); + assertNotNull(returnedDownload); + assertNotNull(returnedDownload.getNodeRef()); + + NodeRef downloadNode = new NodeRef(returnedDownload.getNodeRef()); + + DownloadStatus status = downloadTestUtil.getDownloadStatus(downloadNode); + while (status.getStatus() == DownloadStatus.Status.PENDING) + { + Thread.sleep(PAUSE_TIME); + status = downloadTestUtil.getDownloadStatus(downloadNode); + } + + Set entries = downloadTestUtil.getDownloadEntries(downloadNode); + assertEquals(1, entries.size()); + String modelEntry = downloadTestUtil.getDownloadEntry(entries, modelExportFileName); + assertNotNull(modelEntry); + assertEquals(modelEntry, modelExportFileName); + + // Create download for custom model and its share extension module + response = post("cmm/" + modelName + "/download", customModelAdmin, RestApiUtil.toJsonAsString(new CustomModelDownload()), getExtModuleQS(true), 201); + returnedDownload = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModelDownload.class); + assertNotNull(returnedDownload); + assertNotNull(returnedDownload.getNodeRef()); + + downloadNode = new NodeRef(returnedDownload.getNodeRef()); + + status = downloadTestUtil.getDownloadStatus(downloadNode); + while (status.getStatus() == DownloadStatus.Status.PENDING) + { + Thread.sleep(PAUSE_TIME); + status = downloadTestUtil.getDownloadStatus(downloadNode); + } + + entries = downloadTestUtil.getDownloadEntries(downloadNode); + assertEquals(2, entries.size()); + + modelEntry = downloadTestUtil.getDownloadEntry(entries, modelExportFileName); + assertNotNull(modelEntry); + assertEquals(modelEntry, modelExportFileName); + + String shareExtEntry = downloadTestUtil.getDownloadEntry(entries, shareExtExportFileName); + assertNotNull(shareExtEntry); + assertEquals(shareExtEntry, shareExtExportFileName); + } + + private String getExtModuleQS(boolean withShareExtModule) + { + return "?extModule=" + withShareExtModule; + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/TestCustomProperty.java b/source/test-java/org/alfresco/rest/api/tests/TestCustomProperty.java new file mode 100644 index 0000000000..524405f1a5 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/TestCustomProperty.java @@ -0,0 +1,700 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.alfresco.repo.dictionary.Facetable; +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModelConstraint; +import org.alfresco.rest.api.model.CustomModelNamedValue; +import org.alfresco.rest.api.model.CustomModelProperty; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.junit.Test; + +/** + * Tests the REST API of the properties of the {@link CustomModelService}. + * + * @author Jamal Kaabi-Mofrad + */ +public class TestCustomProperty extends BaseCustomModelApiTest +{ + @Test + public void testCreateProperties() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + { + // Create aspect + String aspectName = "testAspect1" + System.currentTimeMillis(); + CustomAspect aspect = createTypeAspect(CustomAspect.class, modelName, aspectName, null, null, null); + + // Update the Aspect by adding property + CustomAspect payload = new CustomAspect(); + String aspectPropName = "testAspect1Prop1" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property title"); + aspectProp.setMultiValued(true); + aspectProp.setIndexed(true); + aspectProp.setFacetable(Facetable.TRUE); + aspectProp.setIndexTokenisationMode(IndexTokenisationMode.BOTH); + List props = new ArrayList<>(1); + props.add(aspectProp); + payload.setProperties(props); + // Try to update the aspect as a non Admin user + put("cmm/" + modelName + "/aspects", nonAdminUserName, aspectName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 403); + + // Try to update the aspect as a Model Administrator + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 400); // Type name is mandatory + + // Add the mandatory aspect name to the payload + payload.setName(aspectName); + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 200); + + // Retrieve the updated aspect + HttpResponse response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspect.getName(), 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + // Check the aspect's added property + assertEquals(1, returnedAspect.getProperties().size()); + CustomModelProperty customModelProperty = returnedAspect.getProperties().get(0); + assertEquals(aspectPropName, customModelProperty.getName()); + assertEquals("property title", customModelProperty.getTitle()); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + aspectPropName, customModelProperty.getPrefixedName()); + assertEquals("Default data type is 'd:text'.", "d:text", customModelProperty.getDataType()); + assertNull(customModelProperty.getDescription()); + assertTrue(customModelProperty.isMultiValued()); + assertFalse(customModelProperty.isMandatory()); + assertFalse(customModelProperty.isMandatoryEnforced()); + assertNull(customModelProperty.getDefaultValue()); + assertTrue(customModelProperty.isIndexed()); + assertEquals(Facetable.TRUE, customModelProperty.getFacetable()); + assertEquals(IndexTokenisationMode.BOTH, customModelProperty.getIndexTokenisationMode()); + + // Test duplicate property name + aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); // Existing name + aspectProp.setTitle("new property title"); + props = new ArrayList<>(1); + props.add(aspectProp); + payload.setProperties(props); + // Try to update the aspect as a Model Administrator + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 409); // property name already exists + } + + { + // Create type + String typeName = "testType1" + System.currentTimeMillis(); + CustomType type = createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", "test type1 Desc", "cm:content"); + + // Update the Type by adding property + CustomType payload = new CustomType(); + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property title"); + typeProp.setDataType("d:int"); + typeProp.setIndexed(false); + typeProp.setFacetable(Facetable.FALSE); + typeProp.setIndexTokenisationMode(IndexTokenisationMode.FALSE); + List props = new ArrayList<>(1); + props.add(typeProp); + payload.setProperties(props); + + // Try to update the type as a non Admin user + put("cmm/" + modelName + "/types", nonAdminUserName, typeName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 403); + + // Try to update the type as a Model Administrator + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 400); // Type name is mandatory + + // Add the mandatory type name to the payload + payload.setName(typeName); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 200); + + // Retrieve the updated type + HttpResponse response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName(), 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + // Check the type's added property + assertEquals(1, returnedType.getProperties().size()); + CustomModelProperty customModelProperty = returnedType.getProperties().get(0); + assertEquals(typePropName, customModelProperty.getName()); + assertEquals("property title", customModelProperty.getTitle()); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typePropName, customModelProperty.getPrefixedName()); + assertEquals("d:int", customModelProperty.getDataType()); + assertNull(customModelProperty.getDescription()); + assertFalse(customModelProperty.isMultiValued()); + assertFalse(customModelProperty.isMandatory()); + assertFalse(customModelProperty.isMandatoryEnforced()); + assertNull(customModelProperty.getDefaultValue()); + assertFalse(customModelProperty.isIndexed()); + assertEquals(Facetable.FALSE, customModelProperty.getFacetable()); + assertEquals(IndexTokenisationMode.FALSE, customModelProperty.getIndexTokenisationMode()); + + // Retrieve the updated type with all the properties (include inherited) + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName()+SELECT_ALL_PROPS, 200); + returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + assertEquals(3, returnedType.getProperties().size()); + // Check for the inherited properties + assertNotNull(getProperty(returnedType.getProperties(), "content")); // cm:content + assertNotNull(getProperty(returnedType.getProperties(), "name")); // cm:name + + // Create another property and set all of its attributes + payload = new CustomType(); + payload.setName(typeName); + String typePropName2 = "testType1Prop2" + System.currentTimeMillis(); + typeProp = new CustomModelProperty(); + typeProp.setName(typePropName2); + typeProp.setTitle("property2 title"); + typeProp.setDescription("property2 desciption"); + typeProp.setDataType("d:int"); + typeProp.setDefaultValue("0"); + typeProp.setMultiValued(false); + typeProp.setMandatory(true); + typeProp.setMandatoryEnforced(true); + props = new ArrayList<>(1); + props.add(typeProp); + payload.setProperties(props); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 200); + + // Retrieve the updated type + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName(), 200); + returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + // Check the type's added property + assertEquals(2, returnedType.getProperties().size()); + customModelProperty = getProperty(returnedType.getProperties(), typePropName2); + assertNotNull(customModelProperty); + assertEquals(typePropName2, customModelProperty.getName()); + assertEquals("property2 title", customModelProperty.getTitle()); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typePropName2, customModelProperty.getPrefixedName()); + assertEquals("d:int", customModelProperty.getDataType()); + assertEquals("property2 desciption", customModelProperty.getDescription()); + assertFalse(customModelProperty.isMultiValued()); + assertTrue(customModelProperty.isMandatory()); + assertTrue(customModelProperty.isMandatoryEnforced()); + assertEquals("0", customModelProperty.getDefaultValue()); + + // Test duplicate property name + typeProp = new CustomModelProperty(); + typeProp.setName(typePropName2); // Existing name + typeProp.setTitle("new property title"); + typeProp.setDataType("d:text"); + props = new ArrayList<>(1); + props.add(typeProp); + payload.setProperties(props); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 409); // property name already exists + } + } + + @Test + public void testDeleteProperty() throws Exception + { + String modelName = "testModelDeleteProp" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + /* + * Create aspect and update it by adding two properties + */ + String aspectName = "testAspect1" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, null, null, null); + // Update the Aspect by adding property - property one + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + + String aspectPropNameOne = "testAspect1Prop1" + System.currentTimeMillis(); + CustomModelProperty aspectPropOne = new CustomModelProperty(); + aspectPropOne.setName(aspectPropNameOne); + aspectPropOne.setTitle("aspect property one title"); + aspectPropOne.setMultiValued(true); + List props = new ArrayList<>(1); + props.add(aspectPropOne); + aspectPayload.setProperties(props); + // create property one + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 200); + + // Update the Aspect by adding another property - property two + aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + + String aspectPropNameTwo = "testAspect1Prop2" + System.currentTimeMillis(); + CustomModelProperty aspectPropTwo = new CustomModelProperty(); + aspectPropTwo.setName(aspectPropNameTwo); + aspectPropTwo.setTitle("aspect property two title"); + aspectPropTwo.setMandatory(true); + aspectPropTwo.setDataType("d:int"); + aspectPropTwo.setDefaultValue("1"); + props = new ArrayList<>(1); + props.add(aspectPropTwo); + aspectPayload.setProperties(props); + // create property two + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 200); + + // Retrieve the updated aspect + HttpResponse response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + // Check the aspect's added properties + assertEquals(2, returnedAspect.getProperties().size()); + + /* + * Create type and update it by adding two properties + */ + String typeName = "testType1" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", null, "cm:content"); + + // Update the Type by adding property - property one + CustomType typePayload = new CustomType(); + typePayload.setName(typeName); + + String typePropNameOne = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typePropOne = new CustomModelProperty(); + typePropOne.setName(typePropNameOne); + typePropOne.setTitle("type property one title"); + props = new ArrayList<>(1); + props.add(typePropOne); + typePayload.setProperties(props); + // create property one + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 200); + + // Update the Type by adding another property - property two + typePayload = new CustomType(); + typePayload.setName(typeName); + + // Create inline MINMAX constraint + CustomModelConstraint inlineMinMaxConstraint = new CustomModelConstraint(); + inlineMinMaxConstraint.setType("MINMAX"); + inlineMinMaxConstraint.setTitle("test MINMAX title"); + // Create the MinMax constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "100.0")); + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + inlineMinMaxConstraint.setParameters(parameters); + + String typePropNameTwo = "testType1Prop2" + System.currentTimeMillis(); + CustomModelProperty typePropTwo = new CustomModelProperty(); + typePropTwo.setName(typePropNameTwo); + typePropTwo.setTitle("type property two title"); + typePropTwo.setDataType("d:int"); + typePropTwo.setConstraints(Arrays.asList(inlineMinMaxConstraint)); // add the inline constraint + props = new ArrayList<>(1); + props.add(typePropTwo); + typePayload.setProperties(props); + // create property one + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 200); + + // Retrieve the updated type + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + // Check the type's added properties + assertEquals(2, returnedType.getProperties().size()); + + // Delete aspect's property one - model is inactive + { + final String deletePropOneAspectQS = getPropDeleteUpdateQS(aspectPropNameOne, true); + // Try to delete propertyOne from aspect + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, null, deletePropOneAspectQS, 400); // missing payload + + CustomAspect deletePropAspectPayload = new CustomAspect(); + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(deletePropAspectPayload), deletePropOneAspectQS, 400); // missing aspect name + + deletePropAspectPayload.setName(aspectName); + put("cmm/" + modelName + "/aspects", nonAdminUserName, aspectName, RestApiUtil.toJsonAsString(deletePropAspectPayload), deletePropOneAspectQS, 403); // unauthorised + // Delete as a Model Administrator + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(deletePropAspectPayload), deletePropOneAspectQS, 200); + + // Check the property has been deleted + response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 200); + returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + assertEquals(1, returnedAspect.getProperties().size()); + assertFalse("Property one should have been deleted.", aspectPropNameOne.equals(returnedAspect.getProperties().get(0).getName())); + + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(deletePropAspectPayload), deletePropOneAspectQS, 404); //Not found + } + + // Delete type's property two - model is inactive + { + final String deletePropTwoTypeQS = getPropDeleteUpdateQS(typePropNameTwo, true); + // Try to delete propertyOne from type + put("cmm/" + modelName + "/types", customModelAdmin, typeName, null, deletePropTwoTypeQS, 400); // missing payload + + CustomType deletePropTypePayload = new CustomType(); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(deletePropTypePayload), deletePropTwoTypeQS, + 400); // missing type name + + deletePropTypePayload.setName(typeName); + put("cmm/" + modelName + "/types", nonAdminUserName, typeName, RestApiUtil.toJsonAsString(deletePropTypePayload), deletePropTwoTypeQS, 403); // unauthorised + // Delete as a Model Administrator + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(deletePropTypePayload), deletePropTwoTypeQS, 200); + + // Check the property has been deleted + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 200); + returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + assertEquals(1, returnedType.getProperties().size()); + assertFalse("Property two should have been deleted.", typePropNameTwo.equals(returnedType.getProperties().get(0).getName())); + + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(deletePropTypePayload), deletePropTwoTypeQS, 404); //Not found + } + + // Note: at the time of writing, we can't delete a property of an active model, as ModelValidatorImpl.validateIndexedProperty depends on Solr + + } + + @Test + public void testUpdateProperty() throws Exception + { + String modelName = "testModelUpdateProp" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + /* + * Create aspect and update it by adding a property + */ + String aspectName = "testAspect1" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, null, null, null); + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + + String aspectPropName = "testAspect1Prop" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("aspect property title"); + aspectProp.setMultiValued(true); + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 200); + + // Retrieve the updated aspect + HttpResponse response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + // Check the aspect's added property + assertEquals(1, returnedAspect.getProperties().size()); + + /* + * Create type and update it by adding a property + */ + String typeName = "testType1" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeName, "test type1 title", null, "cm:content"); + + // Update the Type by adding property - property one + CustomType typePayload = new CustomType(); + typePayload.setName(typeName); + + // Create inline MINMAX constraint + CustomModelConstraint inlineMinMaxConstraint = new CustomModelConstraint(); + inlineMinMaxConstraint.setType("MINMAX"); + inlineMinMaxConstraint.setTitle("test MINMAX title"); + // Create the MinMax constraint's parameters + List parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "100.0")); + parameters.add(buildNamedValue("minValue", "0.0")); + // Add the parameters into the constraint + inlineMinMaxConstraint.setParameters(parameters); + + String typePropName = "testType1Prop" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setDataType("d:int"); + typeProp.setTitle("type property title"); + typeProp.setDefaultValue("0"); + typeProp.setConstraints(Arrays.asList(inlineMinMaxConstraint)); // add the inline constraint + props = new ArrayList<>(1); + props.add(typeProp); + typePayload.setProperties(props); + // create property + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), SELECT_PROPS_QS, 200); + + // Retrieve the updated type + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + // Check the type's added property + assertEquals(1, returnedType.getProperties().size()); + + // Update aspect's property - model is inactive + { + final String updatePropOneAspectQS = getPropDeleteUpdateQS(aspectPropName, false); + // Try to update property from aspect + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, null, updatePropOneAspectQS, 400); // missing payload + + CustomAspect updatePropAspectPayload = new CustomAspect(); + CustomModelProperty propertyAspect = new CustomModelProperty(); + propertyAspect.setTitle("new Title"); + propertyAspect.setDescription("new Desc"); + propertyAspect.setDataType("d:int"); // the original value was d:text + propertyAspect.setMultiValued(false); // the original value was true + propertyAspect.setMandatory(true); // the original value was false + propertyAspect.setDefaultValue("10"); + List modifiedProp = new ArrayList<>(1); + modifiedProp.add(propertyAspect); + updatePropAspectPayload.setProperties(modifiedProp); + + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(updatePropAspectPayload), updatePropOneAspectQS, 400); // missing aspect name + + // set a random name + updatePropAspectPayload.setName(aspectName + System.currentTimeMillis()); + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(updatePropAspectPayload), updatePropOneAspectQS, 404); // Aspect not found + + // set the correct name + updatePropAspectPayload.setName(aspectName); + // the requested property name dose not match the payload + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(updatePropAspectPayload), updatePropOneAspectQS, 400); + + // set the property name that matches the requested property + propertyAspect.setName(aspectPropName); + put("cmm/" + modelName + "/aspects", nonAdminUserName, aspectName, RestApiUtil.toJsonAsString(updatePropAspectPayload), updatePropOneAspectQS, 403); // unauthorised + // Update as a Model Administrator + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(updatePropAspectPayload), updatePropOneAspectQS, 200); + + // Check the property has been updated + response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 200); + returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + assertEquals(1, returnedAspect.getProperties().size()); + CustomModelProperty modifiedAspectProperty = returnedAspect.getProperties().get(0); + compareCustomModelProperties(propertyAspect, modifiedAspectProperty, "prefixedName", "indexTokenisationMode"); + } + + // Activate the model + CustomModel statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // Update type's property - model is active + { + final String updatePropTwoTypeQS = getPropDeleteUpdateQS(typePropName, false); + CustomType updatePropTypePayload = new CustomType(); + updatePropTypePayload.setName(typeName); + CustomModelProperty propertyType = new CustomModelProperty(); + propertyType.setName(typePropName); + propertyType.setTitle("new Title"); + propertyType.setDescription("new Desc"); + propertyType.setDataType("d:long"); // the original value was d:int + propertyType.setDefaultValue("5"); + List modifiedProp = new ArrayList<>(1); + modifiedProp.add(propertyType); + updatePropTypePayload.setProperties(modifiedProp); + + // Unauthorised + put("cmm/" + modelName + "/types", nonAdminUserName, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 403); + // Try to update an active model as a Model Administrator - Cannot change the data type of the property of an active model + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 409); + + // Set the data type with its original value + propertyType.setDataType("d:int"); + propertyType.setMultiValued(true);// the original value was false + // Cannot change the multi-valued option of the property of an active model + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 409); + + propertyType.setMultiValued(false); + propertyType.setMandatory(true);// the original value was false + // Cannot change the mandatory option of the property of an active model + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 409); + + propertyType.setMandatory(false); + propertyType.setMandatoryEnforced(true);// the original value was false + // Cannot change the mandatory-enforced option of the property of an active model + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 409); + + // Set the mandatory-enforced with its original value + propertyType.setMandatoryEnforced(false); + // Update the MinMax constraint's parameters + parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxValue", "120.0")); // the original value was 100.0 + parameters.add(buildNamedValue("minValue", "20.0")); // the original value was 0.0 + // Add the parameters into the constraint + inlineMinMaxConstraint.setParameters(parameters); + propertyType.setConstraints(Arrays.asList(inlineMinMaxConstraint)); // add the updated inline constraint + + // Try to Update - constraint violation. The default value is 5 which is not in the MinMax range [20, 120] + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 409); + + propertyType.setDefaultValue("25"); // we changed the MinMax constraint to be [20, 120] + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 200); + + // Check the property has been updated + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 200); + returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + assertEquals(1, returnedType.getProperties().size()); + CustomModelProperty modifiedTypeProperty = returnedType.getProperties().get(0); + assertEquals("new Title", modifiedTypeProperty.getTitle()); + assertEquals("new Desc", modifiedTypeProperty.getDescription()); + assertEquals("25", modifiedTypeProperty.getDefaultValue()); + assertEquals("Shouldn't be able to change the data type of the property of an active model." ,"d:int", modifiedTypeProperty.getDataType()); + assertFalse(modifiedTypeProperty.isMandatory()); + assertFalse(modifiedTypeProperty.isMultiValued()); + assertFalse(modifiedTypeProperty.isMandatoryEnforced()); + assertEquals(1, modifiedTypeProperty.getConstraints().size()); + CustomModelConstraint modifiedConstraint = modifiedTypeProperty.getConstraints().get(0); + assertEquals("MINMAX", modifiedConstraint.getType()); + assertEquals("120.0", getParameterSimpleValue(modifiedConstraint.getParameters(), "maxValue")); + assertEquals("20.0", getParameterSimpleValue(modifiedConstraint.getParameters(), "minValue")); + + // Change the constraint type and parameter + inlineMinMaxConstraint.setType("LENGTH"); + inlineMinMaxConstraint.setTitle("test LENGTH title"); + parameters = new ArrayList<>(2); + parameters.add(buildNamedValue("maxLength", "256")); + parameters.add(buildNamedValue("minLength", "0")); + // Add the parameters into the constraint + inlineMinMaxConstraint.setParameters(parameters); + propertyType.setConstraints(Arrays.asList(inlineMinMaxConstraint)); + // LENGTH can only be used with textual data type + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 400); + + //update the property by removing the constraint + propertyType.setConstraints(Collections.emptyList()); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(updatePropTypePayload), updatePropTwoTypeQS, 200); + + response = getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 200); + returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + assertEquals(1, returnedType.getProperties().size()); + modifiedTypeProperty = returnedType.getProperties().get(0); + assertEquals(0, modifiedTypeProperty.getConstraints().size()); + } + } + + @Test + public void testValidatePropertyDefaultValue() throws Exception + { + String modelName = "testModelPropDefaultValue" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + /* + * Create aspect and update it by adding a property + */ + String aspectName = "testAspect1" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, null, null, null); + // Update the Aspect by adding property + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setName(aspectName); + + String aspectPropName = "testAspectProp" + System.currentTimeMillis(); + final String updatePropAspectQS = getPropDeleteUpdateQS(aspectPropName, false); + + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("aspect property title"); + List props = new ArrayList<>(1); + props.add(aspectProp); + aspectPayload.setProperties(props); + + // d:int tests + { + aspectProp.setDataType("d:int"); + aspectProp.setDefaultValue(" ");// space + + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + aspectProp.setDefaultValue("abc"); // text + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + aspectProp.setDefaultValue("1.0"); // double + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + aspectProp.setDefaultValue("1,2,3"); // text + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + } + + // d:float tests + { + aspectProp.setDataType("d:float"); + aspectProp.setDefaultValue(" ");// space + + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + aspectProp.setDefaultValue("abc"); // text + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + aspectProp.setDefaultValue("1,2,3"); // text + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 400); + + aspectProp.setDefaultValue("1.0"); // float + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), SELECT_PROPS_QS, 200); + + aspectProp.setDefaultValue("1.0f"); // float - update + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 200); + + aspectProp.setDefaultValue("1.0d"); // double - update + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 200); + } + + // d:boolean tests + { + aspectProp.setDataType("d:boolean"); + aspectProp.setDefaultValue(" ");// space + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 400); + + aspectProp.setDefaultValue("abc"); // text + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 400); + + aspectProp.setDefaultValue("1"); // number + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 400); + + aspectProp.setDefaultValue("true"); // valid value + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 200); + + aspectProp.setDefaultValue("false"); // valid value + // create property + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), updatePropAspectQS, 200); + } + } + + private String getPropDeleteUpdateQS(String propName, boolean isDelete) + { + String req = (isDelete ? "&delete=" : "&update="); + return SELECT_PROPS_QS + req + propName; + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/TestCustomTypeAspect.java b/source/test-java/org/alfresco/rest/api/tests/TestCustomTypeAspect.java new file mode 100644 index 0000000000..256f31458a --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/TestCustomTypeAspect.java @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.alfresco.repo.dictionary.Facetable; +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.alfresco.rest.api.model.CustomAspect; +import org.alfresco.rest.api.model.CustomModel; +import org.alfresco.rest.api.model.CustomModelProperty; +import org.alfresco.rest.api.model.CustomType; +import org.alfresco.rest.api.model.CustomModel.ModelStatus; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import org.alfresco.service.cmr.dictionary.CustomModelDefinition; +import org.alfresco.service.cmr.dictionary.CustomModelService; +import org.alfresco.service.cmr.dictionary.NamespaceDefinition; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.junit.Test; + +/** + * Tests the REST API of the types and aspects of the {@link CustomModelService}. + * + * @author Jamal Kaabi-Mofrad + */ +public class TestCustomTypeAspect extends BaseCustomModelApiTest +{ + + @Test + public void testCreateAspectsAndTypes_ExistingModel() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + final String aspectName = "testAspect1" + System.currentTimeMillis(); + { + // Add aspect + CustomAspect aspect = new CustomAspect(); + aspect.setName(aspectName); + + // Try to create aspect as a non Admin user + post("cmm/" + modelName + "/aspects", nonAdminUserName, RestApiUtil.toJsonAsString(aspect), 403); + + // Set the aspect's parent with a type name! + aspect.setParentName("cm:content"); + // Try to create an invalid aspect as a Model Administrator + post("cmm/" + modelName + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 409); + + // Create aspect as a Model Administrator + aspect.setParentName(null); + post("cmm/" + modelName + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 201); + + // Create the aspect again - duplicate name + post("cmm/" + modelName + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 409); + + // Retrieve the created aspect + HttpResponse response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspect.getName(), 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + compareCustomTypesAspects(aspect, returnedAspect, "prefixedName"); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + aspectName, returnedAspect.getPrefixedName()); + } + + String typeName = "testType" + System.currentTimeMillis(); + { + // Add type + CustomType type = new CustomType(); + type.setName(typeName); + type.setDescription("test type Desc"); + type.setTitle("test type title"); + type.setParentName("cm:content"); + + // Try to create type as a non Admin user + post("cmm/" + modelName + "/types", nonAdminUserName, RestApiUtil.toJsonAsString(type), 403); + + // Set the type's parent with an aspect name! + type.setParentName("cm:titled"); + // Try to create an invalid type as a Model Administrator + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 409); + + // Create type as a Model Administrator + type.setParentName("cm:content"); + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 201); + + // Create the type again - duplicate name + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 409); + + // Retrieve the created type + HttpResponse response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName(), 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + compareCustomTypesAspects(type, returnedType, "prefixedName"); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typeName, returnedType.getPrefixedName()); + + // for now this is the simplest way to check for the imported namespaces + CustomModelDefinition modelDef = getModelDefinition(modelName); + assertNotNull(modelDef); + Collection importedNamespaces = modelDef.getImportedNamespaces(); + + assertTrue(hasNamespaceUri(importedNamespaces, "http://www.alfresco.org/model/content/1.0")); + assertTrue(hasNamespacePrefix(importedNamespaces, "cm")); + } + + // Existing name + { + // Add aspect + CustomAspect aspect = new CustomAspect(); + // Set the aspect name with an existing type name. The model + // cannot have a type and an aspect with the same name. + aspect.setName(typeName); + post("cmm/" + modelName + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 409); + + CustomType type = new CustomType(); + // Set the type name with an existing aspect name + type.setName(aspectName); + type.setParentName("cm:content"); + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 409); + } + } + + @Test + public void testCreateModel_WithAspectsAndTypes_Invalid() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Add type + { + final String typeURL = "cmm/" + modelName + "/types"; + CustomType type = new CustomType(); + type.setName(null); + type.setDescription("test type Desc"); + + // Try to create a model with an invalid type name (null) + post(typeURL, customModelAdmin, RestApiUtil.toJsonAsString(type), 400); + + // Try to create a model with an invalid type name (name contains ':') + type.setName("prefix:someTypename"); + post(typeURL, customModelAdmin, RestApiUtil.toJsonAsString(type), 400); + + // Try to create a model with an invalid type name (name is empty) + type.setName(""); + post(typeURL, customModelAdmin, RestApiUtil.toJsonAsString(type), 400); + + // Try to create a model with an invalid type name (name contains '<') + type.setName("testType') + aspect.setName("testType>name"); + post(aspectURL, customModelAdmin, RestApiUtil.toJsonAsString(aspect), 400); + } + } + + @Test + public void testCreateAspectsAndTypesWithProperties() throws Exception + { + String modelName = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + { + // Add aspect with property + String aspectName = "testAspect1" + System.currentTimeMillis(); + CustomAspect aspect = new CustomAspect(); + aspect.setName(aspectName); + + String aspectPropName = "testAspect1Prop1" + System.currentTimeMillis(); + CustomModelProperty aspectProp = new CustomModelProperty(); + aspectProp.setName(aspectPropName); + aspectProp.setTitle("property title"); + List props = new ArrayList<>(1); + props.add(aspectProp); + aspect.setProperties(props); + + // Create aspect as a Model Administrator + post("cmm/" + modelName + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 201); + // Retrieve the created aspect + HttpResponse response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspect.getName(), 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + compareCustomTypesAspects(aspect, returnedAspect, "prefixedName", "dataType", "indexTokenisationMode"); + assertEquals(1, returnedAspect.getProperties().size()); + CustomModelProperty customModelProperty = returnedAspect.getProperties().get(0); + assertEquals(aspectPropName, customModelProperty.getName()); + assertEquals("property title", customModelProperty.getTitle()); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + aspectPropName, customModelProperty.getPrefixedName()); + assertEquals("Default data type is 'd:text'.", "d:text", customModelProperty.getDataType()); + // Test default indexing options + assertTrue(customModelProperty.isIndexed()); + assertEquals(Facetable.UNSET, customModelProperty.getFacetable()); + assertEquals(IndexTokenisationMode.TRUE, customModelProperty.getIndexTokenisationMode()); + } + + { + // Add type with properties + String typeName = "testType1" + System.currentTimeMillis(); + CustomType type = new CustomType(); + type.setName(typeName); + type.setDescription("test type1 Desc"); + type.setTitle("test type1 title"); + type.setParentName("cm:content"); + + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setDataType("int"); // data type without dictionary prefix + List props = new ArrayList<>(1); + props.add(typeProp); + type.setProperties(props); + + // Create type as a Model Administrator + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 400); + + typeProp.setDataType("d:int"); + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 201); + + // Retrieve the created type + HttpResponse response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName(), 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + compareCustomTypesAspects(type, returnedType, "prefixedName", "indexTokenisationMode"); + assertEquals("Shouldn't list the inherited properties from 'cm:content'.", 1, returnedType.getProperties().size()); + CustomModelProperty customModelProperty = returnedType.getProperties().get(0); + assertEquals(typePropName, customModelProperty.getName()); + assertEquals("d:int", customModelProperty.getDataType()); + assertEquals(namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typePropName, customModelProperty.getPrefixedName()); + // Test default indexing options + assertTrue(customModelProperty.isIndexed()); + assertEquals(Facetable.UNSET, customModelProperty.getFacetable()); + assertEquals(IndexTokenisationMode.TRUE, customModelProperty.getIndexTokenisationMode()); + } + + { + // Add more types with the same parent + // Test to make sure the inherited properties are excluded properly. + // As without parent’s property exclusion, we won’t be able to + // create multiple types|aspects with the same parent. + String typeName2 = "testType2" + System.currentTimeMillis(); + CustomType type2 = new CustomType(); + type2.setName(typeName2); + type2.setDescription("test type2 Desc"); + type2.setTitle("test type2 title"); + type2.setParentName("cm:content"); + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type2), 201); + + String typeName3 = "testType3" + System.currentTimeMillis(); + CustomType type3 = new CustomType(); + type3.setName(typeName3); + type3.setDescription("test type3 Desc"); + type3.setTitle("test type3 title"); + type3.setParentName("cm:content"); + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type3), 201); + } + + { + // Retrieve the created model + HttpResponse response = getSingle("cmm", customModelAdmin, modelName, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertNull(returnedModel.getTypes()); + assertNull(returnedModel.getAspects()); + + // Retrieve the created model with its types and aspects + response = getSingle("cmm", customModelAdmin, modelName + SELECT_ALL, 200); + returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertNotNull(returnedModel.getTypes()); + assertEquals(3, returnedModel.getTypes().size()); + assertNotNull(returnedModel.getAspects()); + assertEquals(1, returnedModel.getAspects().size()); + } + } + + @Test + //SHA-679 + public void testCustomModelTypesAspectsDependencies() throws Exception + { + String modelNameOne = "testModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelNameOne, namespacePair, ModelStatus.DRAFT, null, "Mark Moe"); + + // Add type + String typeBaseName = "testTypeBase" + System.currentTimeMillis(); + final String typeBaseNameWithPrefix = namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typeBaseName; + createTypeAspect(CustomType.class, modelNameOne, typeBaseName, "test typeBase title", "test typeBase Desc", "cm:content"); + + // Activate the model + CustomModel modelOneStatusPayload = new CustomModel(); + modelOneStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 200); + + HttpResponse response = getSingle("cmm", customModelAdmin, modelNameOne, 200); + CustomModel returnedModel = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomModel.class); + assertEquals(ModelStatus.ACTIVE, returnedModel.getStatus()); + + // Add another type with 'typeBaseName' as its parent + String typeName2 = "testTypeChild" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelNameOne, typeName2, "test typeChild title", "test typeChild Desc", typeBaseNameWithPrefix); + + Paging paging = getPaging(0, Integer.MAX_VALUE); + response = getAll("cmm/" + modelNameOne + "/types", customModelAdmin, paging, 200); + List returnedTypes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), CustomType.class); + assertEquals(2, returnedTypes.size()); + + // Create another model + String modelNameTwo = "testModelTwo" + System.currentTimeMillis(); + Pair modelTwoNamespacePair = getTestNamespaceUriPrefixPair(); + createCustomModel(modelNameTwo, modelTwoNamespacePair, ModelStatus.DRAFT, null, "Admin"); + + // Add a type with 'typeBaseName' from the modelOne as its parent + String modelTwoTypeName = "testModelTwoChild" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelNameTwo, modelTwoTypeName, "test model two type child title", null, typeBaseNameWithPrefix); + + // Try to deactivate modelOne + modelOneStatusPayload = new CustomModel(); + modelOneStatusPayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 409); // ModelTwo depends on ModelOne + + // Activate modelTwo + CustomModel modelTwoStatusPayload = new CustomModel(); + modelTwoStatusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelNameTwo, RestApiUtil.toJsonAsString(modelTwoStatusPayload), SELECT_STATUS_QS, 200); + + // Try to deactivate modelOne again. The dependent model is Active now, however, the result should be the same. + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 409); // ModelTwo depends on ModelOne + + // Deactivate modelTwo + modelTwoStatusPayload = new CustomModel(); + modelTwoStatusPayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelNameTwo, RestApiUtil.toJsonAsString(modelTwoStatusPayload), SELECT_STATUS_QS, 200); + + // Delete the modelTwo's type as a Model Administrator + delete("cmm/" + modelNameTwo + "/types", customModelAdmin, modelTwoTypeName, 204); + + // Try to deactivate modelOne again. There is no dependency + put("cmm", customModelAdmin, modelNameOne, RestApiUtil.toJsonAsString(modelOneStatusPayload), SELECT_STATUS_QS, 200); + + } + + @Test + public void testDeleteTypeAspect() throws Exception + { + final String modelName = "testDeleteTypeModel" + System.currentTimeMillis(); + Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT, null, "Joe Bloggs"); + + // Add type + final String typeName = "testType" + System.currentTimeMillis(); + final String typeNameWithPrefix = namespacePair.getSecond() + QName.NAMESPACE_PREFIX + typeName; + CustomType type = createTypeAspect(CustomType.class, modelName, typeName, "test type title", "test type Desc", "cm:content"); + + // Add aspect + final String aspectName = "testAspect" + System.currentTimeMillis(); + final String aspectNameWithPrefix = namespacePair.getSecond() + QName.NAMESPACE_PREFIX + aspectName; + CustomAspect aspect = createTypeAspect(CustomAspect.class, modelName, aspectName, null, null, null); + + // Delete type + { + // Try to delete the model's type as a non Admin user + delete("cmm/" + modelName + "/types", nonAdminUserName, typeName, 403); + // Delete the model's type as a Model Administrator + delete("cmm/" + modelName + "/types", customModelAdmin, typeName, 204); + // Try to retrieve the deleted type + getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 404); + } + // Delete Aspect + { + // Try to delete the model's aspect as a non Admin user + delete("cmm/" + modelName + "/aspects", nonAdminUserName, aspectName, 403); + // Delete the model's aspect as a Model Administrator + delete("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 204); + // Try to retrieve the deleted aspect + getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 404); + } + + // Create the type again + post("cmm/" + modelName + "/types", customModelAdmin, RestApiUtil.toJsonAsString(type), 201); + // Retrieve the created type + HttpResponse response = getSingle("cmm/" + modelName + "/types", customModelAdmin, type.getName(), 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + compareCustomTypesAspects(type, returnedType, "prefixedName"); + + // Create the aspect again + post("cmm/" + modelName + "/aspects", customModelAdmin, RestApiUtil.toJsonAsString(aspect), 201); + // Retrieve the created aspect + response = getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspect.getName(), 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + compareCustomTypesAspects(aspect, returnedAspect, "prefixedName"); + + // Update the Type by adding property + CustomType payload = new CustomType(); + payload.setName(typeName); + + String typePropName = "testType1Prop1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property title"); + typeProp.setDataType("d:int"); + List props = new ArrayList<>(1); + props.add(typeProp); + payload.setProperties(props); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(payload), SELECT_PROPS_QS, 200); + + // Activate the model + CustomModel statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.ACTIVE); + // Activate the model as a Model Administrator + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // Test for SHA-703 + // Add another type with 'typeName' as its parent + final String childTypeName = "testChildType" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, childTypeName, "test child type title", null, typeNameWithPrefix); + + // Add another aspect with 'aspectName' as its parent + final String childAspectName = "testChildAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, childAspectName, "test child aspect title", null, aspectNameWithPrefix); + + // Delete the model's type as a Model Administrator + delete("cmm/" + modelName + "/types", customModelAdmin, typeName, 409); // Cannot delete a type of an active model + + // Delete the model's aspect as a Model Administrator + delete("cmm/" + modelName + "/aspects", customModelAdmin, childAspectName, 409); // Cannot delete an aspect of an active model + + // Deactivate the model + statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.DRAFT); + // Deactivate the model as a Model Administrator + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // Delete type + { + // Try to delete the model's type (parent) as a Model Administrator + delete("cmm/" + modelName + "/types", customModelAdmin, typeName, 409); // conflict: childTypeName depends on typeName + + // Delete the child type first + delete("cmm/" + modelName + "/types", customModelAdmin, childTypeName, 204); + // Try to retrieve the deleted child type + getSingle("cmm/" + modelName + "/types", customModelAdmin, childTypeName, 404); + + // Now delete the parent type + delete("cmm/" + modelName + "/types", customModelAdmin, typeName, 204); + // Try to retrieve the deleted parent type + getSingle("cmm/" + modelName + "/types", customModelAdmin, typeName, 404); + + } + + // Delete Aspect + { + // Try to delete the model's aspect (parent) as a Model Administrator + delete("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 409); // conflict: childAspectName depends on aspectName + + // Delete the child aspect first + delete("cmm/" + modelName + "/aspects", customModelAdmin, childAspectName, 204); + // Try to retrieve the deleted child aspect + getSingle("cmm/" + modelName + "/aspects", customModelAdmin, childAspectName, 404); + + // Now delete the parent aspect + delete("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 204); + // Try to retrieve the deleted parent aspect + getSingle("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, 404); + } + } + + @Test + public void testUpdateAspectsTypes() throws Exception + { + String modelName = "testModeEditAspectType" + System.currentTimeMillis(); + final Pair namespacePair = getTestNamespaceUriPrefixPair(); + // Create the model as a Model Administrator + createCustomModel(modelName, namespacePair, ModelStatus.DRAFT); + + // Test update aspect + { + // Create aspect + String aspectName = "testAspect" + System.currentTimeMillis(); + createTypeAspect(CustomAspect.class, modelName, aspectName, "title", "desc", null); + // Update the aspect + CustomAspect aspectPayload = new CustomAspect(); + aspectPayload.setDescription(null); + aspectPayload.setTitle("title modified"); + aspectPayload.setParentName("cm:titled"); + + // Try to update the aspect as a non Admin user + put("cmm/" + modelName + "/aspects", nonAdminUserName, aspectName, RestApiUtil.toJsonAsString(aspectPayload), null, 403); + + // Modify the name + aspectPayload.setName(aspectName + "Modified"); + // Try to update the aspect as a Model Administrator + // Note: aspect/type name cannot be modified + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), null, 404); + + aspectPayload.setName(aspectName); + // Update the aspect as a Model Administrator + HttpResponse response = put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), null, 200); + CustomAspect returnedAspect = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomAspect.class); + compareCustomTypesAspects(aspectPayload, returnedAspect, "prefixedName"); + + // Update the aspect with an invalid parent + aspectPayload.setParentName("cm:titled" + System.currentTimeMillis()); + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), null, 409); + + // Activate the model + CustomModel statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // Remove the aspect's parent + // Note: cannot update the parent of an ACTIVE type/aspect. + aspectPayload.setParentName(null); + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), null, 409); + + statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // now update the aspect's parent - model is inactive + put("cmm/" + modelName + "/aspects", customModelAdmin, aspectName, RestApiUtil.toJsonAsString(aspectPayload), null, 200); + } + + // Test update type + { + // Create type + String typeName = "testType" + System.currentTimeMillis(); + createTypeAspect(CustomType.class, modelName, typeName, "title", "desc", "cm:content"); + + // Add property + CustomType addPropertyPayload = new CustomType(); + addPropertyPayload.setName(typeName); + String typePropName = "testTypeProp1" + System.currentTimeMillis(); + CustomModelProperty typeProp = new CustomModelProperty(); + typeProp.setName(typePropName); + typeProp.setTitle("property title"); + typeProp.setDataType("d:text"); + List props = new ArrayList<>(1); + props.add(typeProp); + addPropertyPayload.setProperties(props); + // Create the property + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(addPropertyPayload), SELECT_PROPS_QS, 200); + + // Update the type + CustomType typePayload = new CustomType(); + typePayload.setDescription("desc modified"); + typePayload.setTitle("title modified"); + + // Try to update the type as a non Admin user + put("cmm/" + modelName + "/types", nonAdminUserName, typeName, RestApiUtil.toJsonAsString(typePayload), null, 403); + + // Modify the name + typePayload.setName(typeName + "Modified"); + // Try to update the type as a Model Administrator + // Note: type/type name cannot be modified + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), null, 404); + + typePayload.setName(typeName); + // Update the type as a Model Administrator + HttpResponse response = put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), null, 200); + CustomType returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + assertEquals(typePayload.getDescription(), returnedType.getDescription()); + assertEquals(typePayload.getTitle(), returnedType.getTitle()); + // Check that properties are unmodified + assertNotNull(returnedType.getProperties()); + assertEquals(1, returnedType.getProperties().size()); + assertEquals(typePropName, returnedType.getProperties().iterator().next().getName()); + + // Update the type with an invalid parent + typePayload.setParentName("cm:folder" + System.currentTimeMillis()); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), null, 409); + + // Activate the model + CustomModel statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.ACTIVE); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // Remove the type's parent + // Note: cannot update the parent of an ACTIVE type/type. + typePayload.setParentName("cm:folder"); + put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), null, 409); + + statusPayload = new CustomModel(); + statusPayload.setStatus(ModelStatus.DRAFT); + put("cmm", customModelAdmin, modelName, RestApiUtil.toJsonAsString(statusPayload), SELECT_STATUS_QS, 200); + + // now update the type's parent - model is inactive + response = put("cmm/" + modelName + "/types", customModelAdmin, typeName, RestApiUtil.toJsonAsString(typePayload), null, 200); + returnedType = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), CustomType.class); + assertEquals(typePayload.getParentName(), returnedType.getParentName()); + } + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java b/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java new file mode 100644 index 0000000000..f543d490fc --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.rest.api.tests.util; + +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jackson.map.ObjectMapper; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +/** + * A utility class for Rest API tests + * + * @author Jamal Kaabi-Mofrad + */ +public class RestApiUtil +{ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private RestApiUtil() + { + } + + /** + * Parses the alfresco REST API response for a collection of entries. + * Basically, it looks for the {@code list} JSON object, then it uses + * {@literal Jackson} to convert the list's entries to their corresponding + * POJOs based on the given {@code clazz}. + * + * @param jsonObject the {@code JSONObject} derived from the response + * @param clazz the class which represents the JSON payload + * @return list of POJOs of the given {@code clazz} type + * @throws Exception + */ + public static List parseRestApiEntries(JSONObject jsonObject, Class clazz) throws Exception + { + assertNotNull(jsonObject); + assertNotNull(clazz); + + List models = new ArrayList<>(); + + JSONObject jsonList = (JSONObject) jsonObject.get("list"); + assertNotNull(jsonList); + + JSONArray jsonEntries = (JSONArray) jsonList.get("entries"); + assertNotNull(jsonEntries); + + for (int i = 0; i < jsonEntries.size(); i++) + { + JSONObject jsonEntry = (JSONObject) jsonEntries.get(i); + T pojoModel = parseRestApiEntry(jsonEntry, clazz); + models.add(pojoModel); + } + + return models; + } + + /** + * Parses the alfresco REST API response for a single entry. Basically, it + * looks for the {@code entry} JSON object, then it uses {@literal Jackson} + * to convert it to its corresponding POJO based on the given {@code clazz}. + * + * @param jsonObject the {@code JSONObject} derived from the response + * @param clazz the class which represents the JSON payload + * @return the POJO of the given {@code clazz} type + * @throws Exception + */ + public static T parseRestApiEntry(JSONObject jsonObject, Class clazz) throws Exception + { + assertNotNull(jsonObject); + assertNotNull(clazz); + + JSONObject entry = (JSONObject) jsonObject.get("entry"); + T pojoModel = OBJECT_MAPPER.readValue(entry.toJSONString(), clazz); + assertNotNull(pojoModel); + + return pojoModel; + } + + /** + * Converts the POJO which represents the JSON payload into a JSON string + */ + public static String toJsonAsString(Object object) throws Exception + { + assertNotNull(object); + return OBJECT_MAPPER.writeValueAsString(object); + } +} diff --git a/source/test-resources/custommodel/validExtModule.zip b/source/test-resources/custommodel/validExtModule.zip new file mode 100644 index 0000000000..2929e87fce Binary files /dev/null and b/source/test-resources/custommodel/validExtModule.zip differ diff --git a/source/test-resources/custommodel/validModel.zip b/source/test-resources/custommodel/validModel.zip new file mode 100644 index 0000000000..66bc534d48 Binary files /dev/null and b/source/test-resources/custommodel/validModel.zip differ diff --git a/source/test-resources/custommodel/validModelAndExtModule.zip b/source/test-resources/custommodel/validModelAndExtModule.zip new file mode 100644 index 0000000000..a67211f408 Binary files /dev/null and b/source/test-resources/custommodel/validModelAndExtModule.zip differ