diff --git a/gui/admin-gui/src/main/resources/initial-objects/110-report-user-list.xml b/gui/admin-gui/src/main/resources/initial-objects/110-report-user-list.xml index 3684b9a4449..26d4368676e 100644 --- a/gui/admin-gui/src/main/resources/initial-objects/110-report-user-list.xml +++ b/gui/admin-gui/src/main/resources/initial-objects/110-report-user-list.xml @@ -7,17 +7,16 @@ --> - Users in MidPoint - Users listed in MidPoint. - jasper - - true - PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGphc3BlclJlcG9ydCB4bWxucz0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMgaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L3hzZC9qYXNwZXJyZXBvcnQueHNkIiBuYW1lPSJyZXBvcnRVc2VyTGlzdCIgcGFnZVdpZHRoPSIxMTIwIiBwYWdlSGVpZ2h0PSI1OTUiIG9yaWVudGF0aW9uPSJMYW5kc2NhcGUiIHdoZW5Ob0RhdGFUeXBlPSJBbGxTZWN0aW9uc05vRGV0YWlsIiBjb2x1bW5XaWR0aD0iMTA4MCIgbGVmdE1hcmdpbj0iMjAiIHJpZ2h0TWFyZ2luPSIyMCIgdG9wTWFyZ2luPSIzMCIgYm90dG9tTWFyZ2luPSIzMCIgdXVpZD0iNjdlNDY1YzUtNDZlYS00MGQyLWJlYTAtNDY5YzZjZjM4OTM3Ij4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5wcmludC5rZWVwLmZ1bGwudGV4dCIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLnJlbW92ZS5lbXB0eS5zcGFjZS5iZXR3ZWVuLmNvbHVtbnMiIHZhbHVlPSJ0cnVlIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5yZW1vdmUuZW1wdHkuc3BhY2UuYmV0d2Vlbi5yb3dzIiB2YWx1ZT0idHJ1ZSIvPgoJPHByb3BlcnR5IG5hbWU9Im5ldC5zZi5qYXNwZXJyZXBvcnRzLmV4cG9ydC5wZGYuZm9yY2UubGluZWJyZWFrLnBvbGljeSIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQuY3N2LmV4Y2x1ZGUub3JpZ2luLmJhbmQuMSIgdmFsdWU9InRpdGxlIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0LmNzdi5leGNsdWRlLm9yaWdpbi5iYW5kLjIiIHZhbHVlPSJwYWdlRm9vdGVyIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5leGNsdWRlLm9yaWdpbi5iYW5kLjEiIHZhbHVlPSJwYWdlSGVhZGVyIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5leGNsdWRlLm9yaWdpbi5iYW5kLjIiIHZhbHVlPSJwYWdlRm9vdGVyIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5leGNsdWRlLm9yaWdpbi5rZWVwLmZpcnN0LmJhbmQuMiIgdmFsdWU9ImNvbHVtbkhlYWRlciIvPgoJPHByb3BlcnR5IG5hbWU9Im5ldC5zZi5qYXNwZXJyZXBvcnRzLmV4cG9ydC54bHMuZGV0ZWN0LmNlbGwudHlwZSIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLndyYXAudGV4dCIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLmF1dG8uZml0LnJvdyIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLmF1dG8uZml0LmNvbHVtbiIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5hd3QuaWdub3JlLm1pc3NpbmcuZm9udCIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJpcmVwb3J0Lnpvb20iIHZhbHVlPSIxLjAiLz4KCTxwcm9wZXJ0eSBuYW1lPSJpcmVwb3J0LngiIHZhbHVlPSIxNiIvPgoJPHByb3BlcnR5IG5hbWU9ImlyZXBvcnQueSIgdmFsdWU9IjE0Ii8+Cgk8cHJvcGVydHkgbmFtZT0iY29tLmphc3BlcnNvZnQuc3R1ZGlvLmRhdGEuZGVmYXVsdGRhdGFhZGFwdGVyIiB2YWx1ZT0ibXFsIi8+Cgk8aW1wb3J0IHZhbHVlPSJjb20uZXZvbHZldW0ubWlkcG9pbnQucmVwb3J0LmltcGwuUmVwb3J0VXRpbHMiLz4KCTxzdWJEYXRhc2V0IG5hbWU9InJvbGVzRGF0YXNldCIgdXVpZD0iNjU5ZDNmYmEtZDAzZC00M2JjLThkY2QtNTAyZDAzNDQzZWJlIj4KCQk8cGFyYW1ldGVyIG5hbWU9ImFzc2lnbm1lbnQiIGNsYXNzPSJqYXZhLnV0aWwuTGlzdCIvPgoJCTxxdWVyeVN0cmluZyBsYW5ndWFnZT0ibXFsIj4KCQkJPCFbQ0RBVEFbPGNvZGU+aWYgKGFzc2lnbm1lbnQgIT0gbnVsbCl7cmVwb3J0LnJlc29sdmVSb2xlcyhhc3NpZ25tZW50KX08L2NvZGU+XV0+CgkJPC9xdWVyeVN0cmluZz4KCQk8ZmllbGQgbmFtZT0ibmFtZSIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTwvc3ViRGF0YXNldD4KCTxzdWJEYXRhc2V0IG5hbWU9Im9yZ3NEYXRhc2V0IiB1dWlkPSI2NGE1Yzk0YS1jYmM2LTQyNjgtYmNiMy0wY2VmMzNhYTVlMDIiPgoJCTxwYXJhbWV0ZXIgbmFtZT0iYXNzaWdubWVudCIgY2xhc3M9ImphdmEudXRpbC5MaXN0Ii8+CgkJPHF1ZXJ5U3RyaW5nIGxhbmd1YWdlPSJtcWwiPgoJCQk8IVtDREFUQVs8Y29kZT4KCQkJCQlpZiAoYXNzaWdubWVudCAhPSBudWxsKXsKCQkJCQlyZXBvcnQucmVzb2x2ZU9yZ3MoYXNzaWdubWVudCkKCQkJCQl9CgkJCQk8L2NvZGU+XV0+CgkJPC9xdWVyeVN0cmluZz4KCQk8ZmllbGQgbmFtZT0ibmFtZSIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTwvc3ViRGF0YXNldD4KCTxzdWJEYXRhc2V0IG5hbWU9ImFjY291bnRzRGF0YXNldCIgdXVpZD0iYzRkODQ0ZTctMTRjNC00OWI4LWJmMTEtYzkyMWQ1OGExZDQwIj4KCQk8cGFyYW1ldGVyIG5hbWU9Im9iamVjdFJlZiIgY2xhc3M9ImphdmEudXRpbC5BcnJheUxpc3QiLz4KCQk8cXVlcnlTdHJpbmcgbGFuZ3VhZ2U9Im1xbCI+CgkJCTwhW0NEQVRBWzxmaWx0ZXI+CiAgICAgICAgICAgICAgICA8dHlwZT4KICAgICAgICAgICAgICAgICAgICA8dHlwZT5TaGFkb3dUeXBlPC90eXBlPgogICAgICAgICAgICAgICAgICAgIDxmaWx0ZXI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbk9pZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxleHByZXNzaW9uPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzY3JpcHQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxyZXR1cm5UeXBlPmxpc3Q8L3JldHVyblR5cGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjb2RlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzaWMuZ2V0T2lkcyhvYmplY3RSZWYpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2NvZGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zY3JpcHQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2V4cHJlc3Npb24+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvaW5PaWQ+CiAgICAgICAgICAgICAgICAgICAgPC9maWx0ZXI+CiAgICAgICAgICAgICAgICA8L3R5cGU+CiAgICAgICAgICAgIDwvZmlsdGVyPl1dPgoJCTwvcXVlcnlTdHJpbmc+CgkJPGZpZWxkIG5hbWU9Im5hbWUiIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIi8+CgkJPGZpZWxkIG5hbWU9InJlc291cmNlUmVmIiBjbGFzcz0iY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5PYmplY3RSZWZlcmVuY2VUeXBlIi8+Cgk8L3N1YkRhdGFzZXQ+Cgk8cGFyYW1ldGVyIG5hbWU9ImFjdGl2YXRpb24iIGNsYXNzPSJjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLkFjdGl2YXRpb25TdGF0dXNUeXBlIi8+Cgk8cGFyYW1ldGVyIG5hbWU9Im9yZ2FuaXphdGlvbiIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTxwYXJhbWV0ZXIgbmFtZT0icm9sZSIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTxwYXJhbWV0ZXIgbmFtZT0icmVzb3VyY2UiIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIj4KCQk8cHJvcGVydHkgbmFtZT0ia2V5IiB2YWx1ZT0ib2lkIi8+CgkJPHByb3BlcnR5IG5hbWU9ImxhYmVsIiB2YWx1ZT0ibmFtZSIvPgoJCTxwcm9wZXJ0eSBuYW1lPSJ0YXJnZXRUeXBlIiB2YWx1ZT0iY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5SZXNvdXJjZVR5cGUiLz4KCTwvcGFyYW1ldGVyPgoJPHF1ZXJ5U3RyaW5nIGxhbmd1YWdlPSJtcWwiPgoJCTwhW0NEQVRBWzxmaWx0ZXI+DQogICAgPHR5cGU+DQogICAgICAgIDx0eXBlPlVzZXJUeXBlPC90eXBlPg0KICAgICAgICA8ZmlsdGVyPg0KICAgICAgICAgICAgPGFuZD4NCiAgICAgICAgICAgICAgICA8ZXF1YWw+DQogICAgICAgICAgICAgICAgICAgIDxwYXRoPmFjdGl2YXRpb24vYWRtaW5pc3RyYXRpdmVTdGF0dXM8L3BhdGg+DQogICAgICAgICAgICAgICAgICAgIDxleHByZXNzaW9uPg0KICAgICAgICAgICAgICAgICAgICAgICAgPHF1ZXJ5SW50ZXJwcmV0YXRpb25PZk5vVmFsdWU+ZmlsdGVyQWxsPC9xdWVyeUludGVycHJldGF0aW9uT2ZOb1ZhbHVlPg0KICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGg+JGFjdGl2YXRpb248L3BhdGg+DQogICAgICAgICAgICAgICAgICAgIDwvZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICA8L2VxdWFsPg0KICAgICAgICAgICAgICAgIDxyZWY+DQogICAgICAgICAgICAgICAgICAgIDxwYXRoPmFzc2lnbm1lbnQvdGFyZ2V0UmVmPC9wYXRoPg0KICAgICAgICAgICAgICAgICAgICA8ZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxxdWVyeUludGVycHJldGF0aW9uT2ZOb1ZhbHVlPmZpbHRlckFsbDwvcXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxzY3JpcHQ+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNvZGU+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydCBjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLk9yZ1R5cGU7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydCBjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLk9iamVjdFJlZmVyZW5jZVR5cGU7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoIW9yZ2FuaXphdGlvbikgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG51bGw7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9iamVjdFJlZmVyZW5jZVR5cGUgb3J0ID0gbmV3IE9iamVjdFJlZmVyZW5jZVR5cGUoKTsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcnQuc2V0T2lkKG9yZ2FuaXphdGlvbik7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ydC5zZXRUeXBlKE9yZ1R5cGUuQ09NUExFWF9UWVBFKTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9ydDsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2NvZGU+ICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICA8L3NjcmlwdD4NCiAgICAgICAgICAgICAgICAgICAgPC9leHByZXNzaW9uPg0KICAgICAgICAgICAgICAgIDwvcmVmPg0KICAgICAgICAgICAgICAgIDxyZWY+DQogICAgICAgICAgICAgICAgICAgIDxwYXRoPmFzc2lnbm1lbnQvdGFyZ2V0UmVmPC9wYXRoPg0KICAgICAgICAgICAgICAgICAgICA8ZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxxdWVyeUludGVycHJldGF0aW9uT2ZOb1ZhbHVlPmZpbHRlckFsbDwvcXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxzY3JpcHQ+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNvZGU+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydCBjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLlJvbGVUeXBlOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQgY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5PYmplY3RSZWZlcmVuY2VUeXBlOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFyb2xlKSB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gbnVsbDsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT2JqZWN0UmVmZXJlbmNlVHlwZSBvcnQgPSBuZXcgT2JqZWN0UmVmZXJlbmNlVHlwZSgpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ydC5zZXRPaWQocm9sZSk7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ydC5zZXRUeXBlKFJvbGVUeXBlLkNPTVBMRVhfVFlQRSk7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBvcnQ7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9jb2RlPiAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgPC9zY3JpcHQ+DQogICAgICAgICAgICAgICAgICAgIDwvZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICA8L3JlZj4gIA0KICAgICAgICAgICAgICAgIDxyZWY+DQogICAgICAgICAgICAgICAgICAgIDxwYXRoPmFzc2lnbm1lbnQvY29uc3RydWN0aW9uL3Jlc291cmNlUmVmPC9wYXRoPg0KICAgICAgICAgICAgICAgICAgICA8ZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxxdWVyeUludGVycHJldGF0aW9uT2ZOb1ZhbHVlPmZpbHRlckFsbDwvcXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxzY3JpcHQ+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNvZGU+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydCBjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLlJlc291cmNlVHlwZTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wb3J0IGNvbS5ldm9sdmV1bS5taWRwb2ludC54bWwubnMuX3B1YmxpYy5jb21tb24uY29tbW9uXzMuT2JqZWN0UmVmZXJlbmNlVHlwZTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghcmVzb3VyY2UpIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBudWxsOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPYmplY3RSZWZlcmVuY2VUeXBlIG9ydCA9IG5ldyBPYmplY3RSZWZlcmVuY2VUeXBlKCk7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3J0LnNldE9pZChyZXNvdXJjZSk7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ydC5zZXRUeXBlKFJlc291cmNlVHlwZS5DT01QTEVYX1RZUEUpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gb3J0Ow0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvY29kZT4gICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgIDwvc2NyaXB0Pg0KICAgICAgICAgICAgICAgICAgICA8L2V4cHJlc3Npb24+DQogICAgICAgICAgICAgICAgPC9yZWY+ICANCiAgICAgICAgICAgIDwvYW5kPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgIDwvZmlsdGVyPg0KICAgIDwvdHlwZT4NCjwvZmlsdGVyPl1dPgoJPC9xdWVyeVN0cmluZz4KCTxmaWVsZCBuYW1lPSJvaWQiIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIi8+Cgk8ZmllbGQgbmFtZT0ibmFtZSIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTxmaWVsZCBuYW1lPSJsaW5rUmVmIiBjbGFzcz0iamF2YS51dGlsLkFycmF5TGlzdCIvPgoJPGZpZWxkIG5hbWU9ImFzc2lnbm1lbnQiIGNsYXNzPSJqYXZhLnV0aWwuTGlzdCIvPgoJPGZpZWxkIG5hbWU9ImFjdGl2YXRpb24iIGNsYXNzPSJjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLkFjdGl2YXRpb25UeXBlIi8+Cgk8ZmllbGQgbmFtZT0iZnVsbE5hbWUiIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIi8+Cgk8YmFja2dyb3VuZD4KCQk8YmFuZCBoZWlnaHQ9IjMwIiBzcGxpdFR5cGU9IlN0cmV0Y2giLz4KCTwvYmFja2dyb3VuZD4KCTx0aXRsZT4KCQk8YmFuZCBoZWlnaHQ9IjE0NSIgc3BsaXRUeXBlPSJTdHJldGNoIj4KCQkJPGZyYW1lPgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IlRpdGxlIiBtb2RlPSJPcGFxdWUiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMDgwIiBoZWlnaHQ9IjY3IiBiYWNrY29sb3I9IiMyNjc5OTQiIHV1aWQ9IjQ0YmVkYWNjLWZhMjMtNGZlMS1iNzFmLWU1YWZhOTQzZjU1MyIvPgoJCQkJPHN0YXRpY1RleHQ+CgkJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IlRpdGxlIiB4PSIxMCIgeT0iMTMiIHdpZHRoPSIyNjYiIGhlaWdodD0iMzgiIHV1aWQ9ImYyZDk5Y2FkLTlkODQtNGY1MC1iNDU1LTQ1M2M4N2Y2MmM0YyIvPgoJCQkJCTx0ZXh0RWxlbWVudCB2ZXJ0aWNhbEFsaWdubWVudD0iTWlkZGxlIi8+CgkJCQkJPHRleHQ+PCFbQ0RBVEFbVXNlciBSZXBvcnRdXT48L3RleHQ+CgkJCQk8L3N0YXRpY1RleHQ+CgkJCTwvZnJhbWU+CgkJCTxzdGF0aWNUZXh0PgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IlBhZ2UgaGVhZGVyIiB4PSIyIiB5PSI4NSIgd2lkdGg9IjE1MCIgaGVpZ2h0PSIyMCIgdXVpZD0iYjBiOTcxNGYtOTZmNS00ZjU4LTgyNGItYzgxZmQ0ZDMyMWY3Ii8+CgkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJPHRleHQ+PCFbQ0RBVEFbUmVwb3J0IGdlbmVyYXRlZCBvbjpdXT48L3RleHQ+CgkJCTwvc3RhdGljVGV4dD4KCQkJPHRleHRGaWVsZCBwYXR0ZXJuPSJkZC5NTU1NLnl5eXksIEhIOm1tOnNzIj4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGhlYWRlciIgeD0iMTYwIiB5PSI4NSIgd2lkdGg9IjI1MCIgaGVpZ2h0PSIyMCIgdXVpZD0iMDlhN2UyNzItMjA0ZS00MDc4LThhNWUtZTQ3Mjc1NzQyNGMxIi8+CgkJCQk8dGV4dEVsZW1lbnQgdGV4dEFsaWdubWVudD0iUmlnaHQiIHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiPgoJCQkJCTxmb250IGlzQm9sZD0iZmFsc2UiLz4KCQkJCTwvdGV4dEVsZW1lbnQ+CgkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVtuZXcgamF2YS51dGlsLkRhdGUoKV1dPjwvdGV4dEZpZWxkRXhwcmVzc2lvbj4KCQkJPC90ZXh0RmllbGQ+CgkJCTxzdGF0aWNUZXh0PgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IlBhZ2UgaGVhZGVyIiB4PSIyIiB5PSIxMTUiIHdpZHRoPSIxNTAiIGhlaWdodD0iMjAiIHV1aWQ9IjNmZjc4ZmJmLThmY2UtNDA3Mi1iNjkxLTdhZjA0N2VhOTJhNyIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW051bWJlciBvZiByZWNvcmRzOl1dPjwvdGV4dD4KCQkJPC9zdGF0aWNUZXh0PgoJCQk8dGV4dEZpZWxkIGV2YWx1YXRpb25UaW1lPSJSZXBvcnQiIGlzQmxhbmtXaGVuTnVsbD0idHJ1ZSI+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iUGFnZSBoZWFkZXIiIHg9IjE2MCIgeT0iMTE1IiB3aWR0aD0iMjUwIiBoZWlnaHQ9IjIwIiB1dWlkPSI4OTI1MTIxMS0zZjQ5LTQ3MWQtYjg4ZC01NTY0YzFiZDA0ZDEiLz4KCQkJCTx0ZXh0RWxlbWVudCB0ZXh0QWxpZ25tZW50PSJSaWdodCIgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSI+CgkJCQkJPGZvbnQgaXNCb2xkPSJmYWxzZSIvPgoJCQkJPC90ZXh0RWxlbWVudD4KCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyRWe1JFUE9SVF9DT1VOVH1dXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCTwvdGV4dEZpZWxkPgoJCTwvYmFuZD4KCTwvdGl0bGU+Cgk8cGFnZUhlYWRlcj4KCQk8YmFuZCBzcGxpdFR5cGU9IlN0cmV0Y2giLz4KCTwvcGFnZUhlYWRlcj4KCTxjb2x1bW5IZWFkZXI+CgkJPGJhbmQgaGVpZ2h0PSIxOSIgc3BsaXRUeXBlPSJTdHJldGNoIj4KCQkJPHN0YXRpY1RleHQ+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iQ29sdW1uIGhlYWRlciIgeD0iMCIgeT0iMCIgd2lkdGg9IjI0MCIgaGVpZ2h0PSIxOCIgdXVpZD0iMDQ5ODkwOWItZDNjNS00ZWUzLWI4YzktZjAwYTgwOGVmYTdhIi8+CgkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJPHRleHQ+PCFbQ0RBVEFbTmFtZV1dPjwvdGV4dD4KCQkJPC9zdGF0aWNUZXh0PgoJCQk8c3RhdGljVGV4dD4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJDb2x1bW4gaGVhZGVyIiB4PSIyNDAiIHk9IjAiIHdpZHRoPSIyMjAiIGhlaWdodD0iMTgiIHV1aWQ9Ijg2Yzc0YmViLWJkZGQtNDhjYy05NDVhLTE2N2IyNjFiMWUwYiIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW0Z1bGwgbmFtZV1dPjwvdGV4dD4KCQkJPC9zdGF0aWNUZXh0PgoJCQk8c3RhdGljVGV4dD4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJDb2x1bW4gaGVhZGVyIiB4PSI0NjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTgiIHV1aWQ9Ijg2Yzc0YmViLWJkZGQtNDhjYy05NDVhLTE2N2IyNjFiMWUwYiIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW0FjdGl2YXRpb25dXT48L3RleHQ+CgkJCTwvc3RhdGljVGV4dD4KCQkJPHN0YXRpY1RleHQ+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iQ29sdW1uIGhlYWRlciIgeD0iNTYwIiB5PSIwIiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjE4IiB1dWlkPSIyZmZkMjI4Yi04Yjg3LTRiYzYtYTlkMi0zMWU4YTJkNWRiYjciLz4KCQkJCTx0ZXh0RWxlbWVudCB2ZXJ0aWNhbEFsaWdubWVudD0iTWlkZGxlIi8+CgkJCQk8dGV4dD48IVtDREFUQVtSb2xlXV0+PC90ZXh0PgoJCQk8L3N0YXRpY1RleHQ+CgkJCTxzdGF0aWNUZXh0PgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IkNvbHVtbiBoZWFkZXIiIHg9IjcwMCIgeT0iMCIgd2lkdGg9IjE0MCIgaGVpZ2h0PSIxOCIgdXVpZD0iMmM0ZmFlYjMtZDE5Yy00YzFlLWJmYmYtMWIxMGNlY2U4YWU4Ii8+CgkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJPHRleHQ+PCFbQ0RBVEFbT3JnYW5pemF0aW9uXV0+PC90ZXh0PgoJCQk8L3N0YXRpY1RleHQ+CgkJCTxzdGF0aWNUZXh0PgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IkNvbHVtbiBoZWFkZXIiIHg9Ijg0MCIgeT0iMCIgd2lkdGg9IjI0MCIgaGVpZ2h0PSIxOCIgdXVpZD0iM2U0ZTIzMTktMjUwMi00YTUzLWE2YjQtYzc5Njg1MjFmNmI4Ii8+CgkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJPHRleHQ+PCFbQ0RBVEFbQWNjb3VudF1dPjwvdGV4dD4KCQkJPC9zdGF0aWNUZXh0PgoJCTwvYmFuZD4KCTwvY29sdW1uSGVhZGVyPgoJPGRldGFpbD4KCQk8YmFuZCBoZWlnaHQ9IjI1IiBzcGxpdFR5cGU9IlN0cmV0Y2giPgoJCQk8ZnJhbWU+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iRGV0YWlsIiBzdHJldGNoVHlwZT0iUmVsYXRpdmVUb1RhbGxlc3RPYmplY3QiIG1vZGU9Ik9wYXF1ZSIgeD0iMCIgeT0iMCIgd2lkdGg9IjEwODAiIGhlaWdodD0iMjQiIHV1aWQ9Ijg2ZGI2NTM3LTgxZjAtNDJiMS1iNzRhLWQyYmU3MDkyNzFjZCIvPgoJCQkJPHRleHRGaWVsZCBpc1N0cmV0Y2hXaXRoT3ZlcmZsb3c9InRydWUiIGlzQmxhbmtXaGVuTnVsbD0idHJ1ZSI+CgkJCQkJPHJlcG9ydEVsZW1lbnQgeD0iMCIgeT0iMCIgd2lkdGg9IjI4MCIgaGVpZ2h0PSIyMCIgdXVpZD0iM2M2NjhlZWUtY2QzZS00Njk3LWFmZTMtZWRiNzg5NDUyNWNjIi8+CgkJCQkJPHRleHRGaWVsZEV4cHJlc3Npb24+PCFbQ0RBVEFbJEZ7bmFtZX1dXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCQk8L3RleHRGaWVsZD4KCQkJCTx0ZXh0RmllbGQgaXNTdHJldGNoV2l0aE92ZXJmbG93PSJ0cnVlIiBpc0JsYW5rV2hlbk51bGw9InRydWUiPgoJCQkJCTxyZXBvcnRFbGVtZW50IHg9IjI4MCIgeT0iMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIyMCIgdXVpZD0iYzg5NDhjODUtZWMzMS00YjM5LTg4OWEtNDNjYmNlNzBmN2Y4Ii8+CgkJCQkJPHRleHRGaWVsZEV4cHJlc3Npb24+PCFbQ0RBVEFbJEZ7ZnVsbE5hbWV9XV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJPC90ZXh0RmllbGQ+CgkJCQk8dGV4dEZpZWxkIGlzU3RyZXRjaFdpdGhPdmVyZmxvdz0idHJ1ZSIgaXNCbGFua1doZW5OdWxsPSJ0cnVlIj4KCQkJCQk8cmVwb3J0RWxlbWVudCB4PSI0NjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMjAiIHV1aWQ9Ijg5OWIwNjZkLWQ4Y2YtNDU5NC04M2I0LTFhMzVhOTU0MTY0OCIvPgoJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyRGe2FjdGl2YXRpb259LmdldEFkbWluaXN0cmF0aXZlU3RhdHVzKCldXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCQk8L3RleHRGaWVsZD4KCQkJCTxjb21wb25lbnRFbGVtZW50PgoJCQkJCTxyZXBvcnRFbGVtZW50IHg9IjU2MCIgeT0iMCIgd2lkdGg9IjE0MCIgaGVpZ2h0PSIxOSIgdXVpZD0iNDJhN2YwMWYtMzU4YS00YTBkLWE2YWEtNGM5YzA5M2JlM2M2Ii8+CgkJCQkJPGpyOmxpc3QgeG1sbnM6anI9Imh0dHA6Ly9qYXNwZXJyZXBvcnRzLnNvdXJjZWZvcmdlLm5ldC9qYXNwZXJyZXBvcnRzL2NvbXBvbmVudHMiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMvY29tcG9uZW50cyBodHRwOi8vamFzcGVycmVwb3J0cy5zb3VyY2Vmb3JnZS5uZXQveHNkL2NvbXBvbmVudHMueHNkIiBwcmludE9yZGVyPSJIb3Jpem9udGFsIj4KCQkJCQkJPGRhdGFzZXRSdW4gc3ViRGF0YXNldD0icm9sZXNEYXRhc2V0IiB1dWlkPSIzMDA1ODZkNi0wZjMyLTRhZjktYjI3Zi1hYjk5MTY3NTAxZDUiPgoJCQkJCQkJPHBhcmFtZXRlcnNNYXBFeHByZXNzaW9uPjwhW0NEQVRBW25ldyBIYXNoTWFwKCRQe1JFUE9SVF9QQVJBTUVURVJTX01BUH0pXV0+PC9wYXJhbWV0ZXJzTWFwRXhwcmVzc2lvbj4KCQkJCQkJCTxkYXRhc2V0UGFyYW1ldGVyIG5hbWU9ImFzc2lnbm1lbnQiPgoJCQkJCQkJCTxkYXRhc2V0UGFyYW1ldGVyRXhwcmVzc2lvbj48IVtDREFUQVskRnthc3NpZ25tZW50fV1dPjwvZGF0YXNldFBhcmFtZXRlckV4cHJlc3Npb24+CgkJCQkJCQk8L2RhdGFzZXRQYXJhbWV0ZXI+CgkJCQkJCQk8Y29ubmVjdGlvbkV4cHJlc3Npb24+PCFbQ0RBVEFbJFB7UkVQT1JUX0NPTk5FQ1RJT059XV0+PC9jb25uZWN0aW9uRXhwcmVzc2lvbj4KCQkJCQkJPC9kYXRhc2V0UnVuPgoJCQkJCQk8anI6bGlzdENvbnRlbnRzIGhlaWdodD0iMTgiIHdpZHRoPSIxNDAiPgoJCQkJCQkJPHRleHRGaWVsZCBpc1N0cmV0Y2hXaXRoT3ZlcmZsb3c9InRydWUiIGlzQmxhbmtXaGVuTnVsbD0idHJ1ZSI+CgkJCQkJCQkJPHJlcG9ydEVsZW1lbnQgeD0iMCIgeT0iMCIgd2lkdGg9IjE0MCIgaGVpZ2h0PSIxOCIgdXVpZD0iNjFkYzk0MGUtMWRmNC00NzNkLWExZjUtYzIxZGY2NjZlOGExIi8+CgkJCQkJCQkJPHRleHRGaWVsZEV4cHJlc3Npb24+PCFbQ0RBVEFbJEZ7bmFtZX1dXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCQkJCQk8L3RleHRGaWVsZD4KCQkJCQkJPC9qcjpsaXN0Q29udGVudHM+CgkJCQkJPC9qcjpsaXN0PgoJCQkJPC9jb21wb25lbnRFbGVtZW50PgoJCQkJPGNvbXBvbmVudEVsZW1lbnQ+CgkJCQkJPHJlcG9ydEVsZW1lbnQgeD0iNzAwIiB5PSIwIiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjE5IiB1dWlkPSJlN2Y2NjhmMy05NjY0LTQ2NjAtOGIxNi0wZDI1ZGY3ZDNlMmQiLz4KCQkJCQk8anI6bGlzdCB4bWxuczpqcj0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMvY29tcG9uZW50cyIgeHNpOnNjaGVtYUxvY2F0aW9uPSJodHRwOi8vamFzcGVycmVwb3J0cy5zb3VyY2Vmb3JnZS5uZXQvamFzcGVycmVwb3J0cy9jb21wb25lbnRzIGh0dHA6Ly9qYXNwZXJyZXBvcnRzLnNvdXJjZWZvcmdlLm5ldC94c2QvY29tcG9uZW50cy54c2QiIHByaW50T3JkZXI9Ikhvcml6b250YWwiPgoJCQkJCQk8ZGF0YXNldFJ1biBzdWJEYXRhc2V0PSJvcmdzRGF0YXNldCIgdXVpZD0iYWY2NGMxN2MtMDJlZS00MDQ0LWE5Y2UtOTFhOTBhN2E0ZTA4Ij4KCQkJCQkJCTxwYXJhbWV0ZXJzTWFwRXhwcmVzc2lvbj48IVtDREFUQVtuZXcgSGFzaE1hcCgkUHtSRVBPUlRfUEFSQU1FVEVSU19NQVB9KV1dPjwvcGFyYW1ldGVyc01hcEV4cHJlc3Npb24+CgkJCQkJCQk8ZGF0YXNldFBhcmFtZXRlciBuYW1lPSJhc3NpZ25tZW50Ij4KCQkJCQkJCQk8ZGF0YXNldFBhcmFtZXRlckV4cHJlc3Npb24+PCFbQ0RBVEFbJEZ7YXNzaWdubWVudH1dXT48L2RhdGFzZXRQYXJhbWV0ZXJFeHByZXNzaW9uPgoJCQkJCQkJPC9kYXRhc2V0UGFyYW1ldGVyPgoJCQkJCQkJPGNvbm5lY3Rpb25FeHByZXNzaW9uPjwhW0NEQVRBWyRQe1JFUE9SVF9DT05ORUNUSU9OfV1dPjwvY29ubmVjdGlvbkV4cHJlc3Npb24+CgkJCQkJCTwvZGF0YXNldFJ1bj4KCQkJCQkJPGpyOmxpc3RDb250ZW50cyBoZWlnaHQ9IjE5IiB3aWR0aD0iMTQwIj4KCQkJCQkJCTx0ZXh0RmllbGQgaXNTdHJldGNoV2l0aE92ZXJmbG93PSJ0cnVlIiBpc0JsYW5rV2hlbk51bGw9InRydWUiPgoJCQkJCQkJCTxyZXBvcnRFbGVtZW50IHg9IjAiIHk9IjAiIHdpZHRoPSIxNDAiIGhlaWdodD0iMTkiIHV1aWQ9IjlmNDg5NGYzLTRkYWMtNDgxMy04MGNmLWFkOTcwODM2YzBlYSIvPgoJCQkJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyRGe25hbWV9XV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJCQkJPC90ZXh0RmllbGQ+CgkJCQkJCTwvanI6bGlzdENvbnRlbnRzPgoJCQkJCTwvanI6bGlzdD4KCQkJCTwvY29tcG9uZW50RWxlbWVudD4KCQkJCTxjb21wb25lbnRFbGVtZW50PgoJCQkJCTxyZXBvcnRFbGVtZW50IHg9Ijg0MCIgeT0iMCIgd2lkdGg9IjI0MCIgaGVpZ2h0PSIxOSIgdXVpZD0iYThjZTI1MDAtZDc5OS00OGNjLWEzMzEtNGMxM2EyYWY2NzQ0Ii8+CgkJCQkJPGpyOmxpc3QgeG1sbnM6anI9Imh0dHA6Ly9qYXNwZXJyZXBvcnRzLnNvdXJjZWZvcmdlLm5ldC9qYXNwZXJyZXBvcnRzL2NvbXBvbmVudHMiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMvY29tcG9uZW50cyBodHRwOi8vamFzcGVycmVwb3J0cy5zb3VyY2Vmb3JnZS5uZXQveHNkL2NvbXBvbmVudHMueHNkIiBwcmludE9yZGVyPSJIb3Jpem9udGFsIj4KCQkJCQkJPGRhdGFzZXRSdW4gc3ViRGF0YXNldD0iYWNjb3VudHNEYXRhc2V0IiB1dWlkPSI2ZjE4NWY0OC1kYmMwLTQ3MDMtOGNmMi01ZjljMmM3NDBkOTciPgoJCQkJCQkJPHBhcmFtZXRlcnNNYXBFeHByZXNzaW9uPjwhW0NEQVRBW25ldyBIYXNoTWFwKCRQe1JFUE9SVF9QQVJBTUVURVJTX01BUH0pXV0+PC9wYXJhbWV0ZXJzTWFwRXhwcmVzc2lvbj4KCQkJCQkJCTxkYXRhc2V0UGFyYW1ldGVyIG5hbWU9Im9iamVjdFJlZiI+CgkJCQkJCQkJPGRhdGFzZXRQYXJhbWV0ZXJFeHByZXNzaW9uPjwhW0NEQVRBWyRGe2xpbmtSZWZ9XV0+PC9kYXRhc2V0UGFyYW1ldGVyRXhwcmVzc2lvbj4KCQkJCQkJCTwvZGF0YXNldFBhcmFtZXRlcj4KCQkJCQkJCTxjb25uZWN0aW9uRXhwcmVzc2lvbj48IVtDREFUQVskUHtSRVBPUlRfQ09OTkVDVElPTn1dXT48L2Nvbm5lY3Rpb25FeHByZXNzaW9uPgoJCQkJCQk8L2RhdGFzZXRSdW4+CgkJCQkJCTxqcjpsaXN0Q29udGVudHMgaGVpZ2h0PSIxOSIgd2lkdGg9IjI0MCI+CgkJCQkJCQk8dGV4dEZpZWxkIGlzU3RyZXRjaFdpdGhPdmVyZmxvdz0idHJ1ZSIgaXNCbGFua1doZW5OdWxsPSJ0cnVlIj4KCQkJCQkJCQk8cmVwb3J0RWxlbWVudCB4PSIwIiB5PSIwIiB3aWR0aD0iMjQwIiBoZWlnaHQ9IjE5IiB1dWlkPSI0ZTU5ODlkZi03MzRjLTQxYWQtODQ3Mi1iNmE2Zjg5MzliMjgiLz4KCQkJCQkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVskRntuYW1lfSsiIChSZXNvdXJjZTogIiskRntyZXNvdXJjZVJlZn0uZ2V0TmFtZSgpLmdldE9yaWcoKSsiKSJdXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCQkJCQk8L3RleHRGaWVsZD4KCQkJCQkJPC9qcjpsaXN0Q29udGVudHM+CgkJCQkJPC9qcjpsaXN0PgoJCQkJPC9jb21wb25lbnRFbGVtZW50PgoJCQkJPGxpbmU+CgkJCQkJPHJlcG9ydEVsZW1lbnQgcG9zaXRpb25UeXBlPSJGaXhSZWxhdGl2ZVRvQm90dG9tIiBtb2RlPSJPcGFxdWUiIHg9IjAiIHk9IjE5IiB3aWR0aD0iMTA4MCIgaGVpZ2h0PSIxIiBmb3JlY29sb3I9IiMzMzMzMzMiIHV1aWQ9IjQ3ZjkxODAxLWNmNWYtNGJlZC1iMTljLWNhMzkzMWNiZjk4ZCIvPgoJCQkJPC9saW5lPgoJCQk8L2ZyYW1lPgoJCTwvYmFuZD4KCTwvZGV0YWlsPgoJPGNvbHVtbkZvb3Rlcj4KCQk8YmFuZCBoZWlnaHQ9IjciIHNwbGl0VHlwZT0iU3RyZXRjaCI+CgkJCTxsaW5lPgoJCQkJPHJlcG9ydEVsZW1lbnQgcG9zaXRpb25UeXBlPSJGaXhSZWxhdGl2ZVRvQm90dG9tIiB4PSIwIiB5PSIzIiB3aWR0aD0iMTA4MCIgaGVpZ2h0PSIxIiB1dWlkPSJhNTkxZDRjMS0xY2FkLTRkYTItOWY5ZC0wODFmNTM5ZTkwNDMiLz4KCQkJCTxncmFwaGljRWxlbWVudD4KCQkJCQk8cGVuIGxpbmVXaWR0aD0iMC41IiBsaW5lQ29sb3I9IiM5OTk5OTkiLz4KCQkJCTwvZ3JhcGhpY0VsZW1lbnQ+CgkJCTwvbGluZT4KCQk8L2JhbmQ+Cgk8L2NvbHVtbkZvb3Rlcj4KCTxwYWdlRm9vdGVyPgoJCTxiYW5kIGhlaWdodD0iMzIiIHNwbGl0VHlwZT0iU3RyZXRjaCI+CgkJCTxmcmFtZT4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGZvb3RlciIgbW9kZT0iVHJhbnNwYXJlbnQiIHg9IjAiIHk9IjEiIHdpZHRoPSIxMDgwIiBoZWlnaHQ9IjI0IiB1dWlkPSJmYmU4YWFlNC02NTAwLTQ2OGEtYjFlOC03MDBiNTY5MTM5YTEiLz4KCQkJCTx0ZXh0RmllbGQgcGF0dGVybj0iZGQuTU1NTU0ueXl5eSI+CgkJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IlBhZ2UgZm9vdGVyIiB4PSIyIiB5PSIxIiB3aWR0aD0iMTk3IiBoZWlnaHQ9IjIwIiB1dWlkPSIyOGJiOWI0Ny1hNjljLTQ4ZTEtOTA3My1kNTRkOTI2MjQyZTgiLz4KCQkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBW25ldyBqYXZhLnV0aWwuRGF0ZSgpXV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJPC90ZXh0RmllbGQ+CgkJCQk8dGV4dEZpZWxkPgoJCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGZvb3RlciIgeD0iOTYwIiB5PSIxIiB3aWR0aD0iODAiIGhlaWdodD0iMjAiIHV1aWQ9IjVjMDYyYzY2LWJhNDUtNDI4OC05ZGNkLTI0NmUyOGM1YWY3NSIvPgoJCQkJCTx0ZXh0RWxlbWVudCB0ZXh0QWxpZ25tZW50PSJSaWdodCIgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyJQYWdlICIrJFZ7UEFHRV9OVU1CRVJ9KyIgb2YiXV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJPC90ZXh0RmllbGQ+CgkJCQk8dGV4dEZpZWxkIGV2YWx1YXRpb25UaW1lPSJSZXBvcnQiPgoJCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGZvb3RlciIgeD0iMTA0MCIgeT0iMSIgd2lkdGg9IjQwIiBoZWlnaHQ9IjIwIiB1dWlkPSI5MzRiMTZlOC1jM2ViLTQwMTctODY2YS0wYjc3MzViZjI5MTciLz4KCQkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyIgIiArICRWe1BBR0VfTlVNQkVSfV1dPjwvdGV4dEZpZWxkRXhwcmVzc2lvbj4KCQkJCTwvdGV4dEZpZWxkPgoJCQk8L2ZyYW1lPgoJCTwvYmFuZD4KCTwvcGFnZUZvb3Rlcj4KPC9qYXNwZXJSZXBvcnQ+Cg== - PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8IURPQ1RZUEUgamFzcGVyVGVtcGxhdGUNCiAgUFVCTElDICItLy9KYXNwZXJSZXBvcnRzLy9EVEQgVGVtcGxhdGUvL0VOIg0KICAiaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2R0ZHMvamFzcGVydGVtcGxhdGUuZHRkIj4NCjxqYXNwZXJUZW1wbGF0ZT4NCiAgICAgICAgCTxzdHlsZSBmb250TmFtZT0iRGVqYVZ1IFNhbnMiIGZvbnRTaXplPSIxMCIgaEFsaWduPSJMZWZ0IiBpc0RlZmF1bHQ9InRydWUiIGlzUGRmRW1iZWRkZWQ9InRydWUiIA0KCQkJCSAgIG5hbWU9IkJhc2UiIHBkZkVuY29kaW5nPSJJZGVudGl0eS1IIiBwZGZGb250TmFtZT0iRGVqYVZ1U2Fucy50dGYiIHZBbGlnbj0iTWlkZGxlIj4NCgkJCTwvc3R5bGU+DQoJCQk8c3R5bGUgYmFja2NvbG9yPSIjMjY3OTk0IiBmb250U2l6ZT0iMjYiIGZvcmVjb2xvcj0iI0ZGRkZGRiIgaXNEZWZhdWx0PSJmYWxzZSINCiAgICAgICAgICAgICAgICAgICBtb2RlPSJPcGFxdWUiIG5hbWU9IlRpdGxlIiBzdHlsZT0iQmFzZSIvPiANCgkJCTxzdHlsZSBmb250U2l6ZT0iMTIiIGZvcmVjb2xvcj0iIzAwMDAwMCIgaXNEZWZhdWx0PSJmYWxzZSIgbmFtZT0iUGFnZSBoZWFkZXIiDQogICAgICAgICAgICAgICAgICAgc3R5bGU9IkJhc2UiLz4NCgkJCTxzdHlsZSBiYWNrY29sb3I9IiMzMzMzMzMiIGZvbnRTaXplPSIxMiIgZm9yZWNvbG9yPSIjRkZGRkZGIiBoQWxpZ249IkNlbnRlciINCiAgICAgICAgICAgICAgICAgICBpc0RlZmF1bHQ9ImZhbHNlIiBtb2RlPSJPcGFxdWUiIG5hbWU9IkNvbHVtbiBoZWFkZXIiIHN0eWxlPSJCYXNlIi8+DQoJCQk8c3R5bGUgaXNCb2xkPSJmYWxzZSIgaXNEZWZhdWx0PSJmYWxzZSIgbmFtZT0iRGV0YWlsIiBzdHlsZT0iQmFzZSIvPg0KICAgICAgICAgICAgPHN0eWxlIGlzQm9sZD0iZmFsc2UiIGlzRGVmYXVsdD0iZmFsc2UiIG5hbWU9IkNvZGUiIHN0eWxlPSJCYXNlIiBmb250U2l6ZT0iOSIvPg0KCQkJPHN0eWxlIGZvbnRTaXplPSI5IiBmb3JlY29sb3I9IiMwMDAwMDAiIGlzRGVmYXVsdD0iZmFsc2UiIG5hbWU9IlBhZ2UgZm9vdGVyIg0KICAgICAgICAgICAgICAgICAgIHN0eWxlPSJCYXNlIi8+DQoJCTwvamFzcGVyVGVtcGxhdGU+ - pdf - JRSwapFileVirtualizer - 300 - 10000 - 300000 + Users in MidPoint + Users listed in MidPoint. + jasper + + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGphc3BlclJlcG9ydCB4bWxucz0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMgaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L3hzZC9qYXNwZXJyZXBvcnQueHNkIiBuYW1lPSJyZXBvcnRVc2VyTGlzdCIgcGFnZVdpZHRoPSIxMTIwIiBwYWdlSGVpZ2h0PSI1OTUiIG9yaWVudGF0aW9uPSJMYW5kc2NhcGUiIHdoZW5Ob0RhdGFUeXBlPSJBbGxTZWN0aW9uc05vRGV0YWlsIiBjb2x1bW5XaWR0aD0iMTA4MCIgbGVmdE1hcmdpbj0iMjAiIHJpZ2h0TWFyZ2luPSIyMCIgdG9wTWFyZ2luPSIzMCIgYm90dG9tTWFyZ2luPSIzMCIgdXVpZD0iNjdlNDY1YzUtNDZlYS00MGQyLWJlYTAtNDY5YzZjZjM4OTM3Ij4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5wcmludC5rZWVwLmZ1bGwudGV4dCIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLnJlbW92ZS5lbXB0eS5zcGFjZS5iZXR3ZWVuLmNvbHVtbnMiIHZhbHVlPSJ0cnVlIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5yZW1vdmUuZW1wdHkuc3BhY2UuYmV0d2Vlbi5yb3dzIiB2YWx1ZT0idHJ1ZSIvPgoJPHByb3BlcnR5IG5hbWU9Im5ldC5zZi5qYXNwZXJyZXBvcnRzLmV4cG9ydC5wZGYuZm9yY2UubGluZWJyZWFrLnBvbGljeSIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQuY3N2LmV4Y2x1ZGUub3JpZ2luLmJhbmQuMSIgdmFsdWU9InRpdGxlIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0LmNzdi5leGNsdWRlLm9yaWdpbi5iYW5kLjIiIHZhbHVlPSJwYWdlRm9vdGVyIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5leGNsdWRlLm9yaWdpbi5iYW5kLjEiIHZhbHVlPSJwYWdlSGVhZGVyIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5leGNsdWRlLm9yaWdpbi5iYW5kLjIiIHZhbHVlPSJwYWdlRm9vdGVyIi8+Cgk8cHJvcGVydHkgbmFtZT0ibmV0LnNmLmphc3BlcnJlcG9ydHMuZXhwb3J0Lnhscy5leGNsdWRlLm9yaWdpbi5rZWVwLmZpcnN0LmJhbmQuMiIgdmFsdWU9ImNvbHVtbkhlYWRlciIvPgoJPHByb3BlcnR5IG5hbWU9Im5ldC5zZi5qYXNwZXJyZXBvcnRzLmV4cG9ydC54bHMuZGV0ZWN0LmNlbGwudHlwZSIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLndyYXAudGV4dCIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLmF1dG8uZml0LnJvdyIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5leHBvcnQueGxzLmF1dG8uZml0LmNvbHVtbiIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJuZXQuc2YuamFzcGVycmVwb3J0cy5hd3QuaWdub3JlLm1pc3NpbmcuZm9udCIgdmFsdWU9InRydWUiLz4KCTxwcm9wZXJ0eSBuYW1lPSJpcmVwb3J0Lnpvb20iIHZhbHVlPSIxLjAiLz4KCTxwcm9wZXJ0eSBuYW1lPSJpcmVwb3J0LngiIHZhbHVlPSIxNiIvPgoJPHByb3BlcnR5IG5hbWU9ImlyZXBvcnQueSIgdmFsdWU9IjE0Ii8+Cgk8cHJvcGVydHkgbmFtZT0iY29tLmphc3BlcnNvZnQuc3R1ZGlvLmRhdGEuZGVmYXVsdGRhdGFhZGFwdGVyIiB2YWx1ZT0ibXFsIi8+Cgk8aW1wb3J0IHZhbHVlPSJjb20uZXZvbHZldW0ubWlkcG9pbnQucmVwb3J0LmltcGwuUmVwb3J0VXRpbHMiLz4KCTxzdWJEYXRhc2V0IG5hbWU9InJvbGVzRGF0YXNldCIgdXVpZD0iNjU5ZDNmYmEtZDAzZC00M2JjLThkY2QtNTAyZDAzNDQzZWJlIj4KCQk8cGFyYW1ldGVyIG5hbWU9ImFzc2lnbm1lbnQiIGNsYXNzPSJqYXZhLnV0aWwuTGlzdCIvPgoJCTxxdWVyeVN0cmluZyBsYW5ndWFnZT0ibXFsIj4KCQkJPCFbQ0RBVEFbPGNvZGU+aWYgKGFzc2lnbm1lbnQgIT0gbnVsbCl7cmVwb3J0LnJlc29sdmVSb2xlcyhhc3NpZ25tZW50KX08L2NvZGU+XV0+CgkJPC9xdWVyeVN0cmluZz4KCQk8ZmllbGQgbmFtZT0ibmFtZSIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTwvc3ViRGF0YXNldD4KCTxzdWJEYXRhc2V0IG5hbWU9Im9yZ3NEYXRhc2V0IiB1dWlkPSI2NGE1Yzk0YS1jYmM2LTQyNjgtYmNiMy0wY2VmMzNhYTVlMDIiPgoJCTxwYXJhbWV0ZXIgbmFtZT0iYXNzaWdubWVudCIgY2xhc3M9ImphdmEudXRpbC5MaXN0Ii8+CgkJPHF1ZXJ5U3RyaW5nIGxhbmd1YWdlPSJtcWwiPgoJCQk8IVtDREFUQVs8Y29kZT4KCQkJCQlpZiAoYXNzaWdubWVudCAhPSBudWxsKXsKCQkJCQlyZXBvcnQucmVzb2x2ZU9yZ3MoYXNzaWdubWVudCkKCQkJCQl9CgkJCQk8L2NvZGU+XV0+CgkJPC9xdWVyeVN0cmluZz4KCQk8ZmllbGQgbmFtZT0ibmFtZSIgY2xhc3M9ImphdmEubGFuZy5TdHJpbmciLz4KCTwvc3ViRGF0YXNldD4KCTxzdWJEYXRhc2V0IG5hbWU9ImFjY291bnRzRGF0YXNldCIgdXVpZD0iYzRkODQ0ZTctMTRjNC00OWI4LWJmMTEtYzkyMWQ1OGExZDQwIj4KCQk8cGFyYW1ldGVyIG5hbWU9Im9iamVjdFJlZiIgY2xhc3M9ImphdmEudXRpbC5BcnJheUxpc3QiLz4KCQk8cXVlcnlTdHJpbmcgbGFuZ3VhZ2U9Im1xbCI+CgkJCTwhW0NEQVRBWzxjb2RlPgoJCQlpbXBvcnQgY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5TaGFkb3dUeXBlOwoJCQkKCQkJaWYgKG9iamVjdFJlZiAhPSBudWxsKXsKCQkJCQlyZXBvcnQucmVzb2x2ZUxpbmtSZWZzKG9iamVjdFJlZiwgU2hhZG93VHlwZS5jbGFzcykKCQkJCQl9CgkJCTwvY29kZT5dXT4KCQk8L3F1ZXJ5U3RyaW5nPgoJCTxmaWVsZCBuYW1lPSJuYW1lIiBjbGFzcz0iamF2YS5sYW5nLlN0cmluZyIvPgoJCTxmaWVsZCBuYW1lPSJyZXNvdXJjZVJlZiIgY2xhc3M9ImNvbS5ldm9sdmV1bS5taWRwb2ludC54bWwubnMuX3B1YmxpYy5jb21tb24uY29tbW9uXzMuT2JqZWN0UmVmZXJlbmNlVHlwZSIvPgoJPC9zdWJEYXRhc2V0PgoJPHBhcmFtZXRlciBuYW1lPSJhY3RpdmF0aW9uIiBjbGFzcz0iY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5BY3RpdmF0aW9uU3RhdHVzVHlwZSIvPgoJPHBhcmFtZXRlciBuYW1lPSJvcmdhbml6YXRpb24iIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIi8+Cgk8cGFyYW1ldGVyIG5hbWU9InJvbGUiIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIi8+Cgk8cGFyYW1ldGVyIG5hbWU9InJlc291cmNlIiBjbGFzcz0iamF2YS5sYW5nLlN0cmluZyI+CgkJPHByb3BlcnR5IG5hbWU9ImtleSIgdmFsdWU9Im9pZCIvPgoJCTxwcm9wZXJ0eSBuYW1lPSJsYWJlbCIgdmFsdWU9Im5hbWUiLz4KCQk8cHJvcGVydHkgbmFtZT0idGFyZ2V0VHlwZSIgdmFsdWU9ImNvbS5ldm9sdmV1bS5taWRwb2ludC54bWwubnMuX3B1YmxpYy5jb21tb24uY29tbW9uXzMuUmVzb3VyY2VUeXBlIi8+Cgk8L3BhcmFtZXRlcj4KCTxxdWVyeVN0cmluZyBsYW5ndWFnZT0ibXFsIj4KCQk8IVtDREFUQVs8ZmlsdGVyPg0KICAgIDx0eXBlPg0KICAgICAgICA8dHlwZT5Vc2VyVHlwZTwvdHlwZT4NCiAgICAgICAgPGZpbHRlcj4NCiAgICAgICAgICAgIDxhbmQ+DQogICAgICAgICAgICAgICAgPGVxdWFsPg0KICAgICAgICAgICAgICAgICAgICA8cGF0aD5hY3RpdmF0aW9uL2FkbWluaXN0cmF0aXZlU3RhdHVzPC9wYXRoPg0KICAgICAgICAgICAgICAgICAgICA8ZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxxdWVyeUludGVycHJldGF0aW9uT2ZOb1ZhbHVlPmZpbHRlckFsbDwvcXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT4NCiAgICAgICAgICAgICAgICAgICAgICAgIDxwYXRoPiRhY3RpdmF0aW9uPC9wYXRoPg0KICAgICAgICAgICAgICAgICAgICA8L2V4cHJlc3Npb24+DQogICAgICAgICAgICAgICAgPC9lcXVhbD4NCiAgICAgICAgICAgICAgICA8cmVmPg0KICAgICAgICAgICAgICAgICAgICA8cGF0aD5hc3NpZ25tZW50L3RhcmdldFJlZjwvcGF0aD4NCiAgICAgICAgICAgICAgICAgICAgPGV4cHJlc3Npb24+DQogICAgICAgICAgICAgICAgICAgICAgICA8cXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT5maWx0ZXJBbGw8L3F1ZXJ5SW50ZXJwcmV0YXRpb25PZk5vVmFsdWU+DQogICAgICAgICAgICAgICAgICAgICAgICA8c2NyaXB0Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjb2RlPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQgY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5PcmdUeXBlOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQgY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5PYmplY3RSZWZlcmVuY2VUeXBlOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFvcmdhbml6YXRpb24pIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBudWxsOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPYmplY3RSZWZlcmVuY2VUeXBlIG9ydCA9IG5ldyBPYmplY3RSZWZlcmVuY2VUeXBlKCk7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3J0LnNldE9pZChvcmdhbml6YXRpb24pOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcnQuc2V0VHlwZShPcmdUeXBlLkNPTVBMRVhfVFlQRSk7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBvcnQ7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9jb2RlPiAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgPC9zY3JpcHQ+DQogICAgICAgICAgICAgICAgICAgIDwvZXhwcmVzc2lvbj4NCiAgICAgICAgICAgICAgICA8L3JlZj4NCiAgICAgICAgICAgICAgICA8cmVmPg0KICAgICAgICAgICAgICAgICAgICA8cGF0aD5hc3NpZ25tZW50L3RhcmdldFJlZjwvcGF0aD4NCiAgICAgICAgICAgICAgICAgICAgPGV4cHJlc3Npb24+DQogICAgICAgICAgICAgICAgICAgICAgICA8cXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT5maWx0ZXJBbGw8L3F1ZXJ5SW50ZXJwcmV0YXRpb25PZk5vVmFsdWU+DQogICAgICAgICAgICAgICAgICAgICAgICA8c2NyaXB0Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjb2RlPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQgY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5Sb2xlVHlwZTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wb3J0IGNvbS5ldm9sdmV1bS5taWRwb2ludC54bWwubnMuX3B1YmxpYy5jb21tb24uY29tbW9uXzMuT2JqZWN0UmVmZXJlbmNlVHlwZTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghcm9sZSkgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG51bGw7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9iamVjdFJlZmVyZW5jZVR5cGUgb3J0ID0gbmV3IE9iamVjdFJlZmVyZW5jZVR5cGUoKTsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcnQuc2V0T2lkKHJvbGUpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcnQuc2V0VHlwZShSb2xlVHlwZS5DT01QTEVYX1RZUEUpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gb3J0Ow0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvY29kZT4gICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgIDwvc2NyaXB0Pg0KICAgICAgICAgICAgICAgICAgICA8L2V4cHJlc3Npb24+DQogICAgICAgICAgICAgICAgPC9yZWY+ICANCiAgICAgICAgICAgICAgICA8cmVmPg0KICAgICAgICAgICAgICAgICAgICA8cGF0aD5hc3NpZ25tZW50L2NvbnN0cnVjdGlvbi9yZXNvdXJjZVJlZjwvcGF0aD4NCiAgICAgICAgICAgICAgICAgICAgPGV4cHJlc3Npb24+DQogICAgICAgICAgICAgICAgICAgICAgICA8cXVlcnlJbnRlcnByZXRhdGlvbk9mTm9WYWx1ZT5maWx0ZXJBbGw8L3F1ZXJ5SW50ZXJwcmV0YXRpb25PZk5vVmFsdWU+DQogICAgICAgICAgICAgICAgICAgICAgICA8c2NyaXB0Pg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjb2RlPg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQgY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5SZXNvdXJjZVR5cGU7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltcG9ydCBjb20uZXZvbHZldW0ubWlkcG9pbnQueG1sLm5zLl9wdWJsaWMuY29tbW9uLmNvbW1vbl8zLk9iamVjdFJlZmVyZW5jZVR5cGU7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXJlc291cmNlKSB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gbnVsbDsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT2JqZWN0UmVmZXJlbmNlVHlwZSBvcnQgPSBuZXcgT2JqZWN0UmVmZXJlbmNlVHlwZSgpOyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9ydC5zZXRPaWQocmVzb3VyY2UpOw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcnQuc2V0VHlwZShSZXNvdXJjZVR5cGUuQ09NUExFWF9UWVBFKTsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIG9ydDsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2NvZGU+ICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICA8L3NjcmlwdD4NCiAgICAgICAgICAgICAgICAgICAgPC9leHByZXNzaW9uPg0KICAgICAgICAgICAgICAgIDwvcmVmPiAgDQogICAgICAgICAgICA8L2FuZD4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICA8L2ZpbHRlcj4NCiAgICA8L3R5cGU+DQo8L2ZpbHRlcj5dXT4KCTwvcXVlcnlTdHJpbmc+Cgk8ZmllbGQgbmFtZT0ib2lkIiBjbGFzcz0iamF2YS5sYW5nLlN0cmluZyIvPgoJPGZpZWxkIG5hbWU9Im5hbWUiIGNsYXNzPSJqYXZhLmxhbmcuU3RyaW5nIi8+Cgk8ZmllbGQgbmFtZT0ibGlua1JlZiIgY2xhc3M9ImphdmEudXRpbC5BcnJheUxpc3QiLz4KCTxmaWVsZCBuYW1lPSJhc3NpZ25tZW50IiBjbGFzcz0iamF2YS51dGlsLkxpc3QiLz4KCTxmaWVsZCBuYW1lPSJhY3RpdmF0aW9uIiBjbGFzcz0iY29tLmV2b2x2ZXVtLm1pZHBvaW50LnhtbC5ucy5fcHVibGljLmNvbW1vbi5jb21tb25fMy5BY3RpdmF0aW9uVHlwZSIvPgoJPGZpZWxkIG5hbWU9ImZ1bGxOYW1lIiBjbGFzcz0iamF2YS5sYW5nLlN0cmluZyIvPgoJPGJhY2tncm91bmQ+CgkJPGJhbmQgaGVpZ2h0PSIzMCIgc3BsaXRUeXBlPSJTdHJldGNoIi8+Cgk8L2JhY2tncm91bmQ+Cgk8dGl0bGU+CgkJPGJhbmQgaGVpZ2h0PSIxNDUiIHNwbGl0VHlwZT0iU3RyZXRjaCI+CgkJCTxmcmFtZT4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJUaXRsZSIgbW9kZT0iT3BhcXVlIiB4PSIwIiB5PSIwIiB3aWR0aD0iMTA4MCIgaGVpZ2h0PSI2NyIgYmFja2NvbG9yPSIjMjY3OTk0IiB1dWlkPSI0NGJlZGFjYy1mYTIzLTRmZTEtYjcxZi1lNWFmYTk0M2Y1NTMiLz4KCQkJCTxzdGF0aWNUZXh0PgoJCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJUaXRsZSIgeD0iMTAiIHk9IjEzIiB3aWR0aD0iMjY2IiBoZWlnaHQ9IjM4IiB1dWlkPSJmMmQ5OWNhZC05ZDg0LTRmNTAtYjQ1NS00NTNjODdmNjJjNGMiLz4KCQkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJCTx0ZXh0PjwhW0NEQVRBW1VzZXIgUmVwb3J0XV0+PC90ZXh0PgoJCQkJPC9zdGF0aWNUZXh0PgoJCQk8L2ZyYW1lPgoJCQk8c3RhdGljVGV4dD4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGhlYWRlciIgeD0iMiIgeT0iODUiIHdpZHRoPSIxNTAiIGhlaWdodD0iMjAiIHV1aWQ9ImIwYjk3MTRmLTk2ZjUtNGY1OC04MjRiLWM4MWZkNGQzMjFmNyIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW1JlcG9ydCBnZW5lcmF0ZWQgb246XV0+PC90ZXh0PgoJCQk8L3N0YXRpY1RleHQ+CgkJCTx0ZXh0RmllbGQgcGF0dGVybj0iZGQuTU1NTS55eXl5LCBISDptbTpzcyI+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iUGFnZSBoZWFkZXIiIHg9IjE2MCIgeT0iODUiIHdpZHRoPSIyNTAiIGhlaWdodD0iMjAiIHV1aWQ9IjA5YTdlMjcyLTIwNGUtNDA3OC04YTVlLWU0NzI3NTc0MjRjMSIvPgoJCQkJPHRleHRFbGVtZW50IHRleHRBbGlnbm1lbnQ9IlJpZ2h0IiB2ZXJ0aWNhbEFsaWdubWVudD0iTWlkZGxlIj4KCQkJCQk8Zm9udCBpc0JvbGQ9ImZhbHNlIi8+CgkJCQk8L3RleHRFbGVtZW50PgoJCQkJPHRleHRGaWVsZEV4cHJlc3Npb24+PCFbQ0RBVEFbbmV3IGphdmEudXRpbC5EYXRlKCldXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCTwvdGV4dEZpZWxkPgoJCQk8c3RhdGljVGV4dD4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGhlYWRlciIgeD0iMiIgeT0iMTE1IiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjIwIiB1dWlkPSIzZmY3OGZiZi04ZmNlLTQwNzItYjY5MS03YWYwNDdlYTkyYTciLz4KCQkJCTx0ZXh0RWxlbWVudCB2ZXJ0aWNhbEFsaWdubWVudD0iTWlkZGxlIi8+CgkJCQk8dGV4dD48IVtDREFUQVtOdW1iZXIgb2YgcmVjb3JkczpdXT48L3RleHQ+CgkJCTwvc3RhdGljVGV4dD4KCQkJPHRleHRGaWVsZCBldmFsdWF0aW9uVGltZT0iUmVwb3J0IiBpc0JsYW5rV2hlbk51bGw9InRydWUiPgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IlBhZ2UgaGVhZGVyIiB4PSIxNjAiIHk9IjExNSIgd2lkdGg9IjI1MCIgaGVpZ2h0PSIyMCIgdXVpZD0iODkyNTEyMTEtM2Y0OS00NzFkLWI4OGQtNTU2NGMxYmQwNGQxIi8+CgkJCQk8dGV4dEVsZW1lbnQgdGV4dEFsaWdubWVudD0iUmlnaHQiIHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiPgoJCQkJCTxmb250IGlzQm9sZD0iZmFsc2UiLz4KCQkJCTwvdGV4dEVsZW1lbnQ+CgkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVskVntSRVBPUlRfQ09VTlR9XV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQk8L3RleHRGaWVsZD4KCQk8L2JhbmQ+Cgk8L3RpdGxlPgoJPHBhZ2VIZWFkZXI+CgkJPGJhbmQgc3BsaXRUeXBlPSJTdHJldGNoIi8+Cgk8L3BhZ2VIZWFkZXI+Cgk8Y29sdW1uSGVhZGVyPgoJCTxiYW5kIGhlaWdodD0iMTkiIHNwbGl0VHlwZT0iU3RyZXRjaCI+CgkJCTxzdGF0aWNUZXh0PgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IkNvbHVtbiBoZWFkZXIiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNDAiIGhlaWdodD0iMTgiIHV1aWQ9IjA0OTg5MDliLWQzYzUtNGVlMy1iOGM5LWYwMGE4MDhlZmE3YSIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW05hbWVdXT48L3RleHQ+CgkJCTwvc3RhdGljVGV4dD4KCQkJPHN0YXRpY1RleHQ+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iQ29sdW1uIGhlYWRlciIgeD0iMjQwIiB5PSIwIiB3aWR0aD0iMjIwIiBoZWlnaHQ9IjE4IiB1dWlkPSI4NmM3NGJlYi1iZGRkLTQ4Y2MtOTQ1YS0xNjdiMjYxYjFlMGIiLz4KCQkJCTx0ZXh0RWxlbWVudCB2ZXJ0aWNhbEFsaWdubWVudD0iTWlkZGxlIi8+CgkJCQk8dGV4dD48IVtDREFUQVtGdWxsIG5hbWVdXT48L3RleHQ+CgkJCTwvc3RhdGljVGV4dD4KCQkJPHN0YXRpY1RleHQ+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iQ29sdW1uIGhlYWRlciIgeD0iNDYwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjE4IiB1dWlkPSI4NmM3NGJlYi1iZGRkLTQ4Y2MtOTQ1YS0xNjdiMjYxYjFlMGIiLz4KCQkJCTx0ZXh0RWxlbWVudCB2ZXJ0aWNhbEFsaWdubWVudD0iTWlkZGxlIi8+CgkJCQk8dGV4dD48IVtDREFUQVtBY3RpdmF0aW9uXV0+PC90ZXh0PgoJCQk8L3N0YXRpY1RleHQ+CgkJCTxzdGF0aWNUZXh0PgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IkNvbHVtbiBoZWFkZXIiIHg9IjU2MCIgeT0iMCIgd2lkdGg9IjE0MCIgaGVpZ2h0PSIxOCIgdXVpZD0iMmZmZDIyOGItOGI4Ny00YmM2LWE5ZDItMzFlOGEyZDVkYmI3Ii8+CgkJCQk8dGV4dEVsZW1lbnQgdmVydGljYWxBbGlnbm1lbnQ9Ik1pZGRsZSIvPgoJCQkJPHRleHQ+PCFbQ0RBVEFbUm9sZV1dPjwvdGV4dD4KCQkJPC9zdGF0aWNUZXh0PgoJCQk8c3RhdGljVGV4dD4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJDb2x1bW4gaGVhZGVyIiB4PSI3MDAiIHk9IjAiIHdpZHRoPSIxNDAiIGhlaWdodD0iMTgiIHV1aWQ9IjJjNGZhZWIzLWQxOWMtNGMxZS1iZmJmLTFiMTBjZWNlOGFlOCIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW09yZ2FuaXphdGlvbl1dPjwvdGV4dD4KCQkJPC9zdGF0aWNUZXh0PgoJCQk8c3RhdGljVGV4dD4KCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJDb2x1bW4gaGVhZGVyIiB4PSI4NDAiIHk9IjAiIHdpZHRoPSIyNDAiIGhlaWdodD0iMTgiIHV1aWQ9IjNlNGUyMzE5LTI1MDItNGE1My1hNmI0LWM3OTY4NTIxZjZiOCIvPgoJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCTx0ZXh0PjwhW0NEQVRBW0FjY291bnRdXT48L3RleHQ+CgkJCTwvc3RhdGljVGV4dD4KCQk8L2JhbmQ+Cgk8L2NvbHVtbkhlYWRlcj4KCTxkZXRhaWw+CgkJPGJhbmQgaGVpZ2h0PSIyNSIgc3BsaXRUeXBlPSJTdHJldGNoIj4KCQkJPGZyYW1lPgoJCQkJPHJlcG9ydEVsZW1lbnQgc3R5bGU9IkRldGFpbCIgc3RyZXRjaFR5cGU9IlJlbGF0aXZlVG9UYWxsZXN0T2JqZWN0IiBtb2RlPSJPcGFxdWUiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMDgwIiBoZWlnaHQ9IjI0IiB1dWlkPSI4NmRiNjUzNy04MWYwLTQyYjEtYjc0YS1kMmJlNzA5MjcxY2QiLz4KCQkJCTx0ZXh0RmllbGQgaXNTdHJldGNoV2l0aE92ZXJmbG93PSJ0cnVlIiBpc0JsYW5rV2hlbk51bGw9InRydWUiPgoJCQkJCTxyZXBvcnRFbGVtZW50IHg9IjAiIHk9IjAiIHdpZHRoPSIyODAiIGhlaWdodD0iMjAiIHV1aWQ9IjNjNjY4ZWVlLWNkM2UtNDY5Ny1hZmUzLWVkYjc4OTQ1MjVjYyIvPgoJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyRGe25hbWV9XV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJPC90ZXh0RmllbGQ+CgkJCQk8dGV4dEZpZWxkIGlzU3RyZXRjaFdpdGhPdmVyZmxvdz0idHJ1ZSIgaXNCbGFua1doZW5OdWxsPSJ0cnVlIj4KCQkJCQk8cmVwb3J0RWxlbWVudCB4PSIyODAiIHk9IjAiIHdpZHRoPSIxODAiIGhlaWdodD0iMjAiIHV1aWQ9ImM4OTQ4Yzg1LWVjMzEtNGIzOS04ODlhLTQzY2JjZTcwZjdmOCIvPgoJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyRGe2Z1bGxOYW1lfV1dPjwvdGV4dEZpZWxkRXhwcmVzc2lvbj4KCQkJCTwvdGV4dEZpZWxkPgoJCQkJPHRleHRGaWVsZCBpc1N0cmV0Y2hXaXRoT3ZlcmZsb3c9InRydWUiIGlzQmxhbmtXaGVuTnVsbD0idHJ1ZSI+CgkJCQkJPHJlcG9ydEVsZW1lbnQgeD0iNDYwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjIwIiB1dWlkPSI4OTliMDY2ZC1kOGNmLTQ1OTQtODNiNC0xYTM1YTk1NDE2NDgiLz4KCQkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVskRnthY3RpdmF0aW9ufS5nZXRBZG1pbmlzdHJhdGl2ZVN0YXR1cygpXV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJPC90ZXh0RmllbGQ+CgkJCQk8Y29tcG9uZW50RWxlbWVudD4KCQkJCQk8cmVwb3J0RWxlbWVudCB4PSI1NjAiIHk9IjAiIHdpZHRoPSIxNDAiIGhlaWdodD0iMTkiIHV1aWQ9IjQyYTdmMDFmLTM1OGEtNGEwZC1hNmFhLTRjOWMwOTNiZTNjNiIvPgoJCQkJCTxqcjpsaXN0IHhtbG5zOmpyPSJodHRwOi8vamFzcGVycmVwb3J0cy5zb3VyY2Vmb3JnZS5uZXQvamFzcGVycmVwb3J0cy9jb21wb25lbnRzIiB4c2k6c2NoZW1hTG9jYXRpb249Imh0dHA6Ly9qYXNwZXJyZXBvcnRzLnNvdXJjZWZvcmdlLm5ldC9qYXNwZXJyZXBvcnRzL2NvbXBvbmVudHMgaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L3hzZC9jb21wb25lbnRzLnhzZCIgcHJpbnRPcmRlcj0iSG9yaXpvbnRhbCI+CgkJCQkJCTxkYXRhc2V0UnVuIHN1YkRhdGFzZXQ9InJvbGVzRGF0YXNldCIgdXVpZD0iMzAwNTg2ZDYtMGYzMi00YWY5LWIyN2YtYWI5OTE2NzUwMWQ1Ij4KCQkJCQkJCTxwYXJhbWV0ZXJzTWFwRXhwcmVzc2lvbj48IVtDREFUQVtuZXcgSGFzaE1hcCgkUHtSRVBPUlRfUEFSQU1FVEVSU19NQVB9KV1dPjwvcGFyYW1ldGVyc01hcEV4cHJlc3Npb24+CgkJCQkJCQk8ZGF0YXNldFBhcmFtZXRlciBuYW1lPSJhc3NpZ25tZW50Ij4KCQkJCQkJCQk8ZGF0YXNldFBhcmFtZXRlckV4cHJlc3Npb24+PCFbQ0RBVEFbJEZ7YXNzaWdubWVudH1dXT48L2RhdGFzZXRQYXJhbWV0ZXJFeHByZXNzaW9uPgoJCQkJCQkJPC9kYXRhc2V0UGFyYW1ldGVyPgoJCQkJCQkJPGNvbm5lY3Rpb25FeHByZXNzaW9uPjwhW0NEQVRBWyRQe1JFUE9SVF9DT05ORUNUSU9OfV1dPjwvY29ubmVjdGlvbkV4cHJlc3Npb24+CgkJCQkJCTwvZGF0YXNldFJ1bj4KCQkJCQkJPGpyOmxpc3RDb250ZW50cyBoZWlnaHQ9IjE4IiB3aWR0aD0iMTQwIj4KCQkJCQkJCTx0ZXh0RmllbGQgaXNTdHJldGNoV2l0aE92ZXJmbG93PSJ0cnVlIiBpc0JsYW5rV2hlbk51bGw9InRydWUiPgoJCQkJCQkJCTxyZXBvcnRFbGVtZW50IHg9IjAiIHk9IjAiIHdpZHRoPSIxNDAiIGhlaWdodD0iMTgiIHV1aWQ9IjYxZGM5NDBlLTFkZjQtNDczZC1hMWY1LWMyMWRmNjY2ZThhMSIvPgoJCQkJCQkJCTx0ZXh0RmllbGRFeHByZXNzaW9uPjwhW0NEQVRBWyRGe25hbWV9XV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJCQkJPC90ZXh0RmllbGQ+CgkJCQkJCTwvanI6bGlzdENvbnRlbnRzPgoJCQkJCTwvanI6bGlzdD4KCQkJCTwvY29tcG9uZW50RWxlbWVudD4KCQkJCTxjb21wb25lbnRFbGVtZW50PgoJCQkJCTxyZXBvcnRFbGVtZW50IHg9IjcwMCIgeT0iMCIgd2lkdGg9IjE0MCIgaGVpZ2h0PSIxOSIgdXVpZD0iZTdmNjY4ZjMtOTY2NC00NjYwLThiMTYtMGQyNWRmN2QzZTJkIi8+CgkJCQkJPGpyOmxpc3QgeG1sbnM6anI9Imh0dHA6Ly9qYXNwZXJyZXBvcnRzLnNvdXJjZWZvcmdlLm5ldC9qYXNwZXJyZXBvcnRzL2NvbXBvbmVudHMiIHhzaTpzY2hlbWFMb2NhdGlvbj0iaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2phc3BlcnJlcG9ydHMvY29tcG9uZW50cyBodHRwOi8vamFzcGVycmVwb3J0cy5zb3VyY2Vmb3JnZS5uZXQveHNkL2NvbXBvbmVudHMueHNkIiBwcmludE9yZGVyPSJIb3Jpem9udGFsIj4KCQkJCQkJPGRhdGFzZXRSdW4gc3ViRGF0YXNldD0ib3Jnc0RhdGFzZXQiIHV1aWQ9ImFmNjRjMTdjLTAyZWUtNDA0NC1hOWNlLTkxYTkwYTdhNGUwOCI+CgkJCQkJCQk8cGFyYW1ldGVyc01hcEV4cHJlc3Npb24+PCFbQ0RBVEFbbmV3IEhhc2hNYXAoJFB7UkVQT1JUX1BBUkFNRVRFUlNfTUFQfSldXT48L3BhcmFtZXRlcnNNYXBFeHByZXNzaW9uPgoJCQkJCQkJPGRhdGFzZXRQYXJhbWV0ZXIgbmFtZT0iYXNzaWdubWVudCI+CgkJCQkJCQkJPGRhdGFzZXRQYXJhbWV0ZXJFeHByZXNzaW9uPjwhW0NEQVRBWyRGe2Fzc2lnbm1lbnR9XV0+PC9kYXRhc2V0UGFyYW1ldGVyRXhwcmVzc2lvbj4KCQkJCQkJCTwvZGF0YXNldFBhcmFtZXRlcj4KCQkJCQkJCTxjb25uZWN0aW9uRXhwcmVzc2lvbj48IVtDREFUQVskUHtSRVBPUlRfQ09OTkVDVElPTn1dXT48L2Nvbm5lY3Rpb25FeHByZXNzaW9uPgoJCQkJCQk8L2RhdGFzZXRSdW4+CgkJCQkJCTxqcjpsaXN0Q29udGVudHMgaGVpZ2h0PSIxOSIgd2lkdGg9IjE0MCI+CgkJCQkJCQk8dGV4dEZpZWxkIGlzU3RyZXRjaFdpdGhPdmVyZmxvdz0idHJ1ZSIgaXNCbGFua1doZW5OdWxsPSJ0cnVlIj4KCQkJCQkJCQk8cmVwb3J0RWxlbWVudCB4PSIwIiB5PSIwIiB3aWR0aD0iMTQwIiBoZWlnaHQ9IjE5IiB1dWlkPSI5ZjQ4OTRmMy00ZGFjLTQ4MTMtODBjZi1hZDk3MDgzNmMwZWEiLz4KCQkJCQkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVskRntuYW1lfV1dPjwvdGV4dEZpZWxkRXhwcmVzc2lvbj4KCQkJCQkJCTwvdGV4dEZpZWxkPgoJCQkJCQk8L2pyOmxpc3RDb250ZW50cz4KCQkJCQk8L2pyOmxpc3Q+CgkJCQk8L2NvbXBvbmVudEVsZW1lbnQ+CgkJCQk8Y29tcG9uZW50RWxlbWVudD4KCQkJCQk8cmVwb3J0RWxlbWVudCB4PSI4NDAiIHk9IjAiIHdpZHRoPSIyNDAiIGhlaWdodD0iMTkiIHV1aWQ9ImE4Y2UyNTAwLWQ3OTktNDhjYy1hMzMxLTRjMTNhMmFmNjc0NCIvPgoJCQkJCTxqcjpsaXN0IHhtbG5zOmpyPSJodHRwOi8vamFzcGVycmVwb3J0cy5zb3VyY2Vmb3JnZS5uZXQvamFzcGVycmVwb3J0cy9jb21wb25lbnRzIiB4c2k6c2NoZW1hTG9jYXRpb249Imh0dHA6Ly9qYXNwZXJyZXBvcnRzLnNvdXJjZWZvcmdlLm5ldC9qYXNwZXJyZXBvcnRzL2NvbXBvbmVudHMgaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L3hzZC9jb21wb25lbnRzLnhzZCIgcHJpbnRPcmRlcj0iSG9yaXpvbnRhbCI+CgkJCQkJCTxkYXRhc2V0UnVuIHN1YkRhdGFzZXQ9ImFjY291bnRzRGF0YXNldCIgdXVpZD0iNmYxODVmNDgtZGJjMC00NzAzLThjZjItNWY5YzJjNzQwZDk3Ij4KCQkJCQkJCTxwYXJhbWV0ZXJzTWFwRXhwcmVzc2lvbj48IVtDREFUQVtuZXcgSGFzaE1hcCgkUHtSRVBPUlRfUEFSQU1FVEVSU19NQVB9KV1dPjwvcGFyYW1ldGVyc01hcEV4cHJlc3Npb24+CgkJCQkJCQk8ZGF0YXNldFBhcmFtZXRlciBuYW1lPSJvYmplY3RSZWYiPgoJCQkJCQkJCTxkYXRhc2V0UGFyYW1ldGVyRXhwcmVzc2lvbj48IVtDREFUQVskRntsaW5rUmVmfV1dPjwvZGF0YXNldFBhcmFtZXRlckV4cHJlc3Npb24+CgkJCQkJCQk8L2RhdGFzZXRQYXJhbWV0ZXI+CgkJCQkJCQk8Y29ubmVjdGlvbkV4cHJlc3Npb24+PCFbQ0RBVEFbJFB7UkVQT1JUX0NPTk5FQ1RJT059XV0+PC9jb25uZWN0aW9uRXhwcmVzc2lvbj4KCQkJCQkJPC9kYXRhc2V0UnVuPgoJCQkJCQk8anI6bGlzdENvbnRlbnRzIGhlaWdodD0iMTkiIHdpZHRoPSIyNDAiPgoJCQkJCQkJPHRleHRGaWVsZCBpc1N0cmV0Y2hXaXRoT3ZlcmZsb3c9InRydWUiIGlzQmxhbmtXaGVuTnVsbD0idHJ1ZSI+CgkJCQkJCQkJPHJlcG9ydEVsZW1lbnQgeD0iMCIgeT0iMCIgd2lkdGg9IjI0MCIgaGVpZ2h0PSIxOSIgdXVpZD0iNGU1OTg5ZGYtNzM0Yy00MWFkLTg0NzItYjZhNmY4OTM5YjI4Ii8+CgkJCQkJCQkJPHRleHRGaWVsZEV4cHJlc3Npb24+PCFbQ0RBVEFbJEZ7bmFtZX0rIiAoUmVzb3VyY2U6ICIrJEZ7cmVzb3VyY2VSZWZ9LmdldE5hbWUoKS5nZXRPcmlnKCkrIikiXV0+PC90ZXh0RmllbGRFeHByZXNzaW9uPgoJCQkJCQkJPC90ZXh0RmllbGQ+CgkJCQkJCTwvanI6bGlzdENvbnRlbnRzPgoJCQkJCTwvanI6bGlzdD4KCQkJCTwvY29tcG9uZW50RWxlbWVudD4KCQkJCTxsaW5lPgoJCQkJCTxyZXBvcnRFbGVtZW50IHBvc2l0aW9uVHlwZT0iRml4UmVsYXRpdmVUb0JvdHRvbSIgbW9kZT0iT3BhcXVlIiB4PSIwIiB5PSIxOSIgd2lkdGg9IjEwODAiIGhlaWdodD0iMSIgZm9yZWNvbG9yPSIjMzMzMzMzIiB1dWlkPSI0N2Y5MTgwMS1jZjVmLTRiZWQtYjE5Yy1jYTM5MzFjYmY5OGQiLz4KCQkJCTwvbGluZT4KCQkJPC9mcmFtZT4KCQk8L2JhbmQ+Cgk8L2RldGFpbD4KCTxjb2x1bW5Gb290ZXI+CgkJPGJhbmQgaGVpZ2h0PSI3IiBzcGxpdFR5cGU9IlN0cmV0Y2giPgoJCQk8bGluZT4KCQkJCTxyZXBvcnRFbGVtZW50IHBvc2l0aW9uVHlwZT0iRml4UmVsYXRpdmVUb0JvdHRvbSIgeD0iMCIgeT0iMyIgd2lkdGg9IjEwODAiIGhlaWdodD0iMSIgdXVpZD0iYTU5MWQ0YzEtMWNhZC00ZGEyLTlmOWQtMDgxZjUzOWU5MDQzIi8+CgkJCQk8Z3JhcGhpY0VsZW1lbnQ+CgkJCQkJPHBlbiBsaW5lV2lkdGg9IjAuNSIgbGluZUNvbG9yPSIjOTk5OTk5Ii8+CgkJCQk8L2dyYXBoaWNFbGVtZW50PgoJCQk8L2xpbmU+CgkJPC9iYW5kPgoJPC9jb2x1bW5Gb290ZXI+Cgk8cGFnZUZvb3Rlcj4KCQk8YmFuZCBoZWlnaHQ9IjMyIiBzcGxpdFR5cGU9IlN0cmV0Y2giPgoJCQk8ZnJhbWU+CgkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iUGFnZSBmb290ZXIiIG1vZGU9IlRyYW5zcGFyZW50IiB4PSIwIiB5PSIxIiB3aWR0aD0iMTA4MCIgaGVpZ2h0PSIyNCIgdXVpZD0iZmJlOGFhZTQtNjUwMC00NjhhLWIxZTgtNzAwYjU2OTEzOWExIi8+CgkJCQk8dGV4dEZpZWxkIHBhdHRlcm49ImRkLk1NTU1NLnl5eXkiPgoJCQkJCTxyZXBvcnRFbGVtZW50IHN0eWxlPSJQYWdlIGZvb3RlciIgeD0iMiIgeT0iMSIgd2lkdGg9IjE5NyIgaGVpZ2h0PSIyMCIgdXVpZD0iMjhiYjliNDctYTY5Yy00OGUxLTkwNzMtZDU0ZDkyNjI0MmU4Ii8+CgkJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVtuZXcgamF2YS51dGlsLkRhdGUoKV1dPjwvdGV4dEZpZWxkRXhwcmVzc2lvbj4KCQkJCTwvdGV4dEZpZWxkPgoJCQkJPHRleHRGaWVsZD4KCQkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iUGFnZSBmb290ZXIiIHg9Ijk2MCIgeT0iMSIgd2lkdGg9IjgwIiBoZWlnaHQ9IjIwIiB1dWlkPSI1YzA2MmM2Ni1iYTQ1LTQyODgtOWRjZC0yNDZlMjhjNWFmNzUiLz4KCQkJCQk8dGV4dEVsZW1lbnQgdGV4dEFsaWdubWVudD0iUmlnaHQiIHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVsiUGFnZSAiKyRWe1BBR0VfTlVNQkVSfSsiIG9mIl1dPjwvdGV4dEZpZWxkRXhwcmVzc2lvbj4KCQkJCTwvdGV4dEZpZWxkPgoJCQkJPHRleHRGaWVsZCBldmFsdWF0aW9uVGltZT0iUmVwb3J0Ij4KCQkJCQk8cmVwb3J0RWxlbWVudCBzdHlsZT0iUGFnZSBmb290ZXIiIHg9IjEwNDAiIHk9IjEiIHdpZHRoPSI0MCIgaGVpZ2h0PSIyMCIgdXVpZD0iOTM0YjE2ZTgtYzNlYi00MDE3LTg2NmEtMGI3NzM1YmYyOTE3Ii8+CgkJCQkJPHRleHRFbGVtZW50IHZlcnRpY2FsQWxpZ25tZW50PSJNaWRkbGUiLz4KCQkJCQk8dGV4dEZpZWxkRXhwcmVzc2lvbj48IVtDREFUQVsiICIgKyAkVntQQUdFX05VTUJFUn1dXT48L3RleHRGaWVsZEV4cHJlc3Npb24+CgkJCQk8L3RleHRGaWVsZD4KCQkJPC9mcmFtZT4KCQk8L2JhbmQ+Cgk8L3BhZ2VGb290ZXI+CjwvamFzcGVyUmVwb3J0Pgo= + PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8IURPQ1RZUEUgamFzcGVyVGVtcGxhdGUNCiAgUFVCTElDICItLy9KYXNwZXJSZXBvcnRzLy9EVEQgVGVtcGxhdGUvL0VOIg0KICAiaHR0cDovL2phc3BlcnJlcG9ydHMuc291cmNlZm9yZ2UubmV0L2R0ZHMvamFzcGVydGVtcGxhdGUuZHRkIj4NCjxqYXNwZXJUZW1wbGF0ZT4NCiAgICAgICAgCTxzdHlsZSBmb250TmFtZT0iRGVqYVZ1IFNhbnMiIGZvbnRTaXplPSIxMCIgaEFsaWduPSJMZWZ0IiBpc0RlZmF1bHQ9InRydWUiIGlzUGRmRW1iZWRkZWQ9InRydWUiIA0KCQkJCSAgIG5hbWU9IkJhc2UiIHBkZkVuY29kaW5nPSJJZGVudGl0eS1IIiBwZGZGb250TmFtZT0iRGVqYVZ1U2Fucy50dGYiIHZBbGlnbj0iTWlkZGxlIj4NCgkJCTwvc3R5bGU+DQoJCQk8c3R5bGUgYmFja2NvbG9yPSIjMjY3OTk0IiBmb250U2l6ZT0iMjYiIGZvcmVjb2xvcj0iI0ZGRkZGRiIgaXNEZWZhdWx0PSJmYWxzZSINCiAgICAgICAgICAgICAgICAgICBtb2RlPSJPcGFxdWUiIG5hbWU9IlRpdGxlIiBzdHlsZT0iQmFzZSIvPiANCgkJCTxzdHlsZSBmb250U2l6ZT0iMTIiIGZvcmVjb2xvcj0iIzAwMDAwMCIgaXNEZWZhdWx0PSJmYWxzZSIgbmFtZT0iUGFnZSBoZWFkZXIiDQogICAgICAgICAgICAgICAgICAgc3R5bGU9IkJhc2UiLz4NCgkJCTxzdHlsZSBiYWNrY29sb3I9IiMzMzMzMzMiIGZvbnRTaXplPSIxMiIgZm9yZWNvbG9yPSIjRkZGRkZGIiBoQWxpZ249IkNlbnRlciINCiAgICAgICAgICAgICAgICAgICBpc0RlZmF1bHQ9ImZhbHNlIiBtb2RlPSJPcGFxdWUiIG5hbWU9IkNvbHVtbiBoZWFkZXIiIHN0eWxlPSJCYXNlIi8+DQoJCQk8c3R5bGUgaXNCb2xkPSJmYWxzZSIgaXNEZWZhdWx0PSJmYWxzZSIgbmFtZT0iRGV0YWlsIiBzdHlsZT0iQmFzZSIvPg0KICAgICAgICAgICAgPHN0eWxlIGlzQm9sZD0iZmFsc2UiIGlzRGVmYXVsdD0iZmFsc2UiIG5hbWU9IkNvZGUiIHN0eWxlPSJCYXNlIiBmb250U2l6ZT0iOSIvPg0KCQkJPHN0eWxlIGZvbnRTaXplPSI5IiBmb3JlY29sb3I9IiMwMDAwMDAiIGlzRGVmYXVsdD0iZmFsc2UiIG5hbWU9IlBhZ2UgZm9vdGVyIg0KICAgICAgICAgICAgICAgICAgIHN0eWxlPSJCYXNlIi8+DQoJCTwvamFzcGVyVGVtcGxhdGU+ + pdf + JRSwapFileVirtualizer + 300 + 10000 + 300000 diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java index fe731b04b67..3ee803bfe3f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java @@ -1,1884 +1,1888 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.lens; - -import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.FOCUS_OPERATION; -import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.RESOURCE_OBJECT_OPERATION; -import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; -import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerables; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import javax.annotation.PostConstruct; -import javax.xml.bind.JAXBElement; -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.api.context.SynchronizationIntent; -import org.apache.commons.lang.BooleanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.common.SynchronizationUtils; -import com.evolveum.midpoint.model.api.ModelAuthorizationAction; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.ProgressInformation; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; -import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.crypto.EncryptionException; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; -import com.evolveum.midpoint.prism.xnode.XNodeFactory; -import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.api.*; -import com.evolveum.midpoint.repo.common.expression.*; -import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.internals.InternalsConfig; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.schema.util.ExceptionUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.security.api.MidPointPrincipal; -import com.evolveum.midpoint.security.api.OwnerResolver; -import com.evolveum.midpoint.security.api.SecurityContextManager; -import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; -import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; -import com.evolveum.prism.xml.ns._public.types_3.RawType; - -/** - * @author semancik - */ -@Component -public class ChangeExecutor { - - private static final Trace LOGGER = TraceManager.getTrace(ChangeExecutor.class); - - private static final String OPERATION_EXECUTE_DELTA = ChangeExecutor.class.getName() + ".executeDelta"; - private static final String OPERATION_EXECUTE = ChangeExecutor.class.getName() + ".execute"; - private static final String OPERATION_EXECUTE_FOCUS = OPERATION_EXECUTE + ".focus"; - private static final String OPERATION_EXECUTE_PROJECTION = OPERATION_EXECUTE + ".projection"; - private static final String OPERATION_LINK_ACCOUNT = ChangeExecutor.class.getName() + ".linkShadow"; - private static final String OPERATION_UNLINK_ACCOUNT = ChangeExecutor.class.getName() + ".unlinkShadow"; - private static final String OPERATION_UPDATE_SITUATION_IN_SHADOW = ChangeExecutor.class.getName() + ".updateSituationInShadow"; - - @Autowired private transient TaskManager taskManager; - @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; - @Autowired private ProvisioningService provisioning; - @Autowired private PrismContext prismContext; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private SecurityEnforcer securityEnforcer; - @Autowired private SecurityContextManager securityContextManager; - @Autowired private Clock clock; - @Autowired private ModelObjectResolver objectResolver; - @Autowired private OperationalDataManager metadataManager; - @Autowired private CredentialsProcessor credentialsProcessor; - - private PrismObjectDefinition userDefinition = null; - private PrismObjectDefinition shadowDefinition = null; - - @PostConstruct - private void locateDefinitions() { - userDefinition = prismContext.getSchemaRegistry() - .findObjectDefinitionByCompileTimeClass(UserType.class); - shadowDefinition = prismContext.getSchemaRegistry() - .findObjectDefinitionByCompileTimeClass(ShadowType.class); - } - - // returns true if current operation has to be restarted, see - // ObjectAlreadyExistsException handling (TODO specify more exactly) - public boolean executeChanges(LensContext context, Task task, - OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException, PreconditionViolationException, PolicyViolationException { - - OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE); - - try { - - // FOCUS - - context.checkAbortRequested(); - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - ObjectDelta focusDelta = focusContext.getWaveExecutableDelta(context.getExecutionWave()); - - focusDelta = applyPendingObjectPolicyStateModifications(focusContext, focusDelta); - focusDelta = applyPendingAssignmentPolicyStateModifications(focusContext, focusDelta); - - if (focusDelta == null && !context.hasProjectionChange()) { - LOGGER.trace("Skipping focus change execute, because user delta is null"); - } else { - - if (focusDelta == null) { - focusDelta = focusContext.getObjectAny().createModifyDelta(); - } - - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); - applyObjectPolicy(focusContext, focusDelta, archetypePolicy); - - OperationResult subResult = result.createSubresult( - OPERATION_EXECUTE_FOCUS + "." + focusContext.getObjectTypeClass().getSimpleName()); - - try { - // Will remove credential deltas or hash them - focusDelta = credentialsProcessor.transformFocusExecutionDelta(context, focusDelta); - } catch (EncryptionException e) { - recordFatalError(subResult, result, null, e); - result.computeStatus(); - throw new SystemException(e.getMessage(), e); - } - - applyLastProvisioningTimestamp(context, focusDelta); - - try { - - context.reportProgress(new ProgressInformation(FOCUS_OPERATION, ENTERING)); - - ConflictResolutionType conflictResolution = ModelExecuteOptions - .getFocusConflictResolution(context.getOptions()); - - executeDelta(focusDelta, focusContext, context, null, conflictResolution, null, task, subResult); - - if (focusDelta.isAdd() && focusDelta.getOid() != null) { - // The watcher can already exist; if the OID was pre-existing in the object. - if (context.getFocusConflictWatcher() == null) { - ConflictWatcher watcher = context - .createAndRegisterFocusConflictWatcher(focusDelta.getOid(), cacheRepositoryService); - watcher.setExpectedVersion(focusDelta.getObjectToAdd().getVersion()); - } - } - subResult.computeStatus(); - - } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException | RuntimeException e) { - recordFatalError(subResult, result, null, e); - throw e; - - } catch (PreconditionViolationException e) { - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Modification precondition failed for {}: {}", focusContext.getHumanReadableName(), - e.getMessage()); - } - // TODO: fatal error if the conflict resolution is "error" (later) - result.recordHandledError(e); - throw e; - - } catch (ObjectAlreadyExistsException e) { - subResult.computeStatus(); - if (!subResult.isSuccess() && !subResult.isHandledError()) { - subResult.recordFatalError(e); - } - result.computeStatusComposite(); - throw e; - } finally { - context.reportProgress(new ProgressInformation(FOCUS_OPERATION, subResult)); - } - } - } - - // PROJECTIONS - - context.checkAbortRequested(); - - boolean restartRequested = false; - - for (LensProjectionContext projCtx : context.getProjectionContexts()) { - if (projCtx.getWave() != context.getExecutionWave()) { - LOGGER.trace("Skipping projection context {} because its wave ({}) is different from execution wave ({})", - projCtx.toHumanReadableString(), projCtx.getWave(), context.getExecutionWave()); - continue; - } - - if (!projCtx.isCanProject()) { - LOGGER.trace("Skipping projection context {} because canProject is false", projCtx.toHumanReadableString()); - continue; - } - - // we should not get here, but just to be sure - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { - LOGGER.trace("Skipping ignored projection context {}", projCtx.toHumanReadableString()); - continue; - } - - OperationResult subResult = result.subresult(OPERATION_EXECUTE_PROJECTION + "." + projCtx.getObjectTypeClass().getSimpleName()) - .addParam("resource", projCtx.getResource()) - .addArbitraryObjectAsContext("discriminator", projCtx.getResourceShadowDiscriminator()) - .build(); - - PrismObject shadowAfterModification = null; - try { - LOGGER.trace("Executing projection context {}", projCtx.toHumanReadableString()); - - context.checkAbortRequested(); - - context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, - projCtx.getResourceShadowDiscriminator(), ENTERING)); - - executeReconciliationScript(projCtx, context, BeforeAfterType.BEFORE, task, subResult); - - ObjectDelta projDelta = projCtx.getExecutableDelta(); - - if (shouldBeDeleted(projDelta, projCtx)) { - projDelta = prismContext.deltaFactory().object() - .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); - } - - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - if (context.getFocusContext() != null - && context.getFocusContext().getDelta() != null - && context.getFocusContext().getDelta().isDelete() - && context.getOptions() != null - && ModelExecuteOptions.isForce(context.getOptions())) { - if (projDelta == null) { - projDelta = prismContext.deltaFactory().object() - .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); - } - } - if (projDelta != null && projDelta.isDelete()) { - - shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, - subResult); - - } - } else { - - if (projDelta == null || projDelta.isEmpty()) { - LOGGER.trace("No change for {}", projCtx.getResourceShadowDiscriminator()); - shadowAfterModification = projCtx.getObjectCurrent(); - if (focusContext != null) { - updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); - } - - // Make sure post-reconcile delta is always executed, - // even if there is no change - executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, - subResult); - - subResult.computeStatus(); - subResult.recordNotApplicableIfUnknown(); - continue; - - } else if (projDelta.isDelete() && projCtx.getResourceShadowDiscriminator() != null - && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { - // HACK ... for higher-order context check if this was - // already deleted - LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, - projCtx); - if (lowerOrderContext != null && lowerOrderContext.isDelete()) { - // We assume that this was already executed - subResult.setStatus(OperationResultStatus.NOT_APPLICABLE); - continue; - } - } - - shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); - - if (projCtx.isAdd() && shadowAfterModification != null) { - projCtx.setExists(true); - } - - } - - subResult.computeStatus(); - if (focusContext != null) { - updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); - } - - executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult); - - subResult.computeStatus(); - subResult.recordNotApplicableIfUnknown(); - - } catch (SchemaException | ObjectNotFoundException | PreconditionViolationException | CommunicationException | - ConfigurationException | SecurityViolationException | PolicyViolationException | ExpressionEvaluationException | RuntimeException | Error e) { - recordProjectionExecutionException(e, projCtx, subResult, SynchronizationPolicyDecision.BROKEN); - - // We still want to update the links here. E.g. this may be live sync case where we discovered new account - // try to reconcile, but the reconciliation fails. We still want this shadow linked to user. - if (focusContext != null) { - updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); - } - - ModelImplUtils.handleConnectorErrorCriticality(projCtx.getResource(), e, subResult); - - } catch (ObjectAlreadyExistsException e) { - - // This exception is quite special. We have to decide how bad this really is. - // This may be rename conflict. Which would be bad. - // Or this may be attempt to create account that already exists and just needs - // to be linked. Which is no big deal and consistency mechanism (discovery) will - // easily handle that. In that case it is done in "another task" which is - // quasi-asynchornously executed from provisioning by calling notifyChange. - // Once that is done then the account is already linked. And all we need to do - // is to restart this whole operation. - - // check if this is a repeated attempt - OAEE was not handled - // correctly, e.g. if creating "Users" user in AD, whereas - // "Users" is SAM Account Name which is used by a built-in group - // - in such case, mark the context as broken - if (isRepeatedAlreadyExistsException(projCtx)) { - // This is the bad case. Currently we do not do anything more intelligent than to look for - // repeated error. If we get OAEE twice then this is bad and we thow up. - // TODO: do something smarter here - LOGGER.debug("Repeated ObjectAlreadyExistsException detected, marking projection {} as broken", projCtx.toHumanReadableString()); - recordProjectionExecutionException(e, projCtx, subResult, - SynchronizationPolicyDecision.BROKEN); - continue; - } - - // in his case we do not need to set account context as - // broken, instead we need to restart projector for this - // context to recompute new account or find out if the - // account was already linked.. - // and also do not set fatal error to the operation result, this - // is a special case - // if it is fatal, it will be set later - // but we need to set some result - subResult.recordSuccess(); - restartRequested = true; - LOGGER.debug("ObjectAlreadyExistsException for projection {}, requesting projector restart", projCtx.toHumanReadableString()); - // we will process remaining projections when retrying the wave - break; - - } finally { - context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, - projCtx.getResourceShadowDiscriminator(), subResult)); - } - } - - // Result computation here needs to be slightly different - result.computeStatusComposite(); - return restartRequested; - - } catch (Throwable t) { - result.recordThrowableIfNeeded(t); // last resort: to avoid UNKNOWN subresults - throw t; - } - } - - private ObjectDelta applyPendingObjectPolicyStateModifications(LensFocusContext focusContext, - ObjectDelta focusDelta) throws SchemaException { - for (ItemDelta, ?> itemDelta : focusContext.getPendingObjectPolicyStateModifications()) { - focusDelta = focusContext.swallowToDelta(focusDelta, itemDelta); - } - focusContext.clearPendingObjectPolicyStateModifications(); - return focusDelta; - } - - private ObjectDelta applyPendingAssignmentPolicyStateModifications(LensFocusContext focusContext, ObjectDelta focusDelta) - throws SchemaException { - for (Map.Entry>> entry : focusContext - .getPendingAssignmentPolicyStateModifications().entrySet()) { - PlusMinusZero mode = entry.getKey().mode; - if (mode == PlusMinusZero.MINUS) { - continue; // this assignment is being thrown out anyway, so let's ignore it (at least for now) - } - AssignmentType assignmentToFind = entry.getKey().assignment; - List> modifications = entry.getValue(); - if (modifications.isEmpty()) { - continue; - } - LOGGER.trace("Applying policy state modifications for {} ({}):\n{}", assignmentToFind, mode, - DebugUtil.debugDumpLazily(modifications)); - if (mode == PlusMinusZero.ZERO) { - if (assignmentToFind.getId() == null) { - throw new IllegalStateException("Existing assignment with null id: " + assignmentToFind); - } - for (ItemDelta, ?> modification : modifications) { - focusDelta = focusContext.swallowToDelta(focusDelta, modification); - } - } else { - assert mode == PlusMinusZero.PLUS; - if (focusDelta != null && focusDelta.isAdd()) { - swallowIntoValues(((FocusType) focusDelta.getObjectToAdd().asObjectable()).getAssignment(), - assignmentToFind, modifications); - } else { - ContainerDelta assignmentDelta = focusDelta != null ? - focusDelta.findContainerDelta(FocusType.F_ASSIGNMENT) : null; - if (assignmentDelta == null) { - throw new IllegalStateException( - "We have 'plus' assignment to modify but there's no assignment delta. Assignment=" - + assignmentToFind + ", objectDelta=" + focusDelta); - } - if (assignmentDelta.isReplace()) { - swallowIntoValues(asContainerables(assignmentDelta.getValuesToReplace()), assignmentToFind, - modifications); - } else if (assignmentDelta.isAdd()) { - swallowIntoValues(asContainerables(assignmentDelta.getValuesToAdd()), assignmentToFind, - modifications); - } else { - throw new IllegalStateException( - "We have 'plus' assignment to modify but there're no values to add or replace in assignment delta. Assignment=" - + assignmentToFind + ", objectDelta=" + focusDelta); - } - } - } - } - focusContext.clearPendingAssignmentPolicyStateModifications(); - return focusDelta; - } - - private void swallowIntoValues(Collection assignments, AssignmentType assignmentToFind, List> modifications) - throws SchemaException { - for (AssignmentType assignment : assignments) { - PrismContainerValue> pcv = assignment.asPrismContainerValue(); - PrismContainerValue> pcvToFind = assignmentToFind.asPrismContainerValue(); - if (pcv.representsSameValue(pcvToFind, false) || pcv.equals(pcvToFind, EquivalenceStrategy.REAL_VALUE_CONSIDER_DIFFERENT_IDS)) { - // TODO what if ID of the assignment being added is changed in repo? Hopefully it will be not. - for (ItemDelta, ?> modification : modifications) { - ItemPath newParentPath = modification.getParentPath().rest(2); // killing assignment + ID - ItemDelta, ?> pathRelativeModification = modification.cloneWithChangedParentPath(newParentPath); - pathRelativeModification.applyTo(pcv); - } - return; - } - } - // TODO change to warning - throw new IllegalStateException("We have 'plus' assignment to modify but it couldn't be found in assignment delta. Assignment=" + assignmentToFind + ", new assignments=" + assignments); - } - - private void applyLastProvisioningTimestamp(LensContext context, ObjectDelta focusDelta) throws SchemaException { - if (!context.hasProjectionChange()) { - return; - } - if (focusDelta.isAdd()) { - - PrismObject objectToAdd = focusDelta.getObjectToAdd(); - PrismContainer metadataContainer = objectToAdd.findOrCreateContainer(ObjectType.F_METADATA); - metadataContainer.getRealValue().setLastProvisioningTimestamp(clock.currentTimeXMLGregorianCalendar()); - - } else if (focusDelta.isModify()) { - - PropertyDelta provTimestampDelta = prismContext.deltaFactory().property().createModificationReplaceProperty( - ItemPath.create(ObjectType.F_METADATA, MetadataType.F_LAST_PROVISIONING_TIMESTAMP), - context.getFocusContext().getObjectDefinition(), - clock.currentTimeXMLGregorianCalendar()); - focusDelta.addModification(provTimestampDelta); - - } - } - - private boolean shouldBeDeleted(ObjectDelta accDelta, LensProjectionContext accCtx) { - return (accDelta == null || accDelta.isEmpty()) - && (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE - || accCtx.getSynchronizationIntent() == SynchronizationIntent.DELETE); - } - - private boolean isRepeatedAlreadyExistsException( - LensProjectionContext projContext) { - int deltas = projContext.getExecutedDeltas().size(); - LOGGER.trace("isRepeatedAlreadyExistsException starting; number of executed deltas = {}", deltas); - if (deltas < 2) { - return false; - } - LensObjectDeltaOperation lastDeltaOp = projContext.getExecutedDeltas().get(deltas - 1); - LensObjectDeltaOperation previousDeltaOp = projContext.getExecutedDeltas() - .get(deltas - 2); - // TODO check also previous execution result to see if it's - // AlreadyExistException? - ObjectDelta lastDelta = lastDeltaOp.getObjectDelta(); - ObjectDelta previousDelta = previousDeltaOp.getObjectDelta(); - boolean rv; - if (lastDelta.isAdd() && previousDelta.isAdd()) { - rv = isEquivalentAddDelta(lastDelta.getObjectToAdd(), previousDelta.getObjectToAdd()); - } else if (lastDelta.isModify() && previousDelta.isModify()) { - rv = isEquivalentModifyDelta(lastDelta.getModifications(), previousDelta.getModifications()); - } else { - rv = false; - } - LOGGER.trace( - "isRepeatedAlreadyExistsException returning {}; based of comparison of previousDelta:\n{}\nwith lastDelta:\n{}", - rv, previousDelta, lastDelta); - return rv; - } - - private boolean isEquivalentModifyDelta(Collection extends ItemDelta, ?>> modifications1, - Collection extends ItemDelta, ?>> modifications2) { - Collection extends ItemDelta, ?>> attrDeltas1 = ItemDeltaCollectionsUtil - .findItemDeltasSubPath(modifications1, ShadowType.F_ATTRIBUTES); - Collection extends ItemDelta, ?>> attrDeltas2 = ItemDeltaCollectionsUtil - .findItemDeltasSubPath(modifications2, ShadowType.F_ATTRIBUTES); - //noinspection unchecked,RedundantCast - return MiscUtil.unorderedCollectionEquals((Collection) attrDeltas1, (Collection) attrDeltas2); - } - - private boolean isEquivalentAddDelta(PrismObject object1, PrismObject object2) { - PrismContainer attributes1 = object1.findContainer(ShadowType.F_ATTRIBUTES); - PrismContainer attributes2 = object2.findContainer(ShadowType.F_ATTRIBUTES); - if (attributes1 == null || attributes2 == null || attributes1.size() != 1 - || attributes2.size() != 1) { // suspicious cases - return false; - } - return attributes1.getValue().equivalent(attributes2.getValue()); - } - - private void applyObjectPolicy(LensFocusContext focusContext, - ObjectDelta focusDelta, ArchetypePolicyType archetypePolicy) { - if (archetypePolicy == null) { - return; - } - PrismObject objectNew = focusContext.getObjectNew(); - if (focusDelta.isAdd() && objectNew.getOid() == null) { - - for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { - processItemConstraint(focusContext, objectNew, itemConstraintType); - } - // Deprecated - for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { - processItemConstraint(focusContext, objectNew, itemConstraintType); - } - - } - } - - private void processItemConstraint(LensFocusContext focusContext, PrismObject objectNew, ItemConstraintType itemConstraintType) { - if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { - ItemPath itemPath = itemConstraintType.getPath().getItemPath(); - PrismProperty prop = objectNew.findProperty(itemPath); - String stringValue = prop.getRealValue().toString(); - focusContext.setOid(stringValue); - } - } - - private void recordProjectionExecutionException(Throwable e, - LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) { - subResult.recordFatalError(e); - LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e); - if (decision != null) { - accCtx.setSynchronizationPolicyDecision(decision); - } - } - - private void recordFatalError(OperationResult subResult, OperationResult result, String message, - Throwable e) { - if (message == null) { - message = e.getMessage(); - } - subResult.recordFatalError(e); - if (result != null) { - result.computeStatusComposite(); - } - } - - /** - * Make sure that the account is linked (or unlinked) as needed. - */ - private void updateLinks(LensContext> context, - LensFocusContext focusObjectContext, LensProjectionContext projCtx, - PrismObject shadowAfterModification, - Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { - if (focusObjectContext == null) { - return; - } - Class objectTypeClass = focusObjectContext.getObjectTypeClass(); - if (!FocusType.class.isAssignableFrom(objectTypeClass)) { - return; - } - //noinspection unchecked - LensFocusContext focusContext = (LensFocusContext) focusObjectContext; - - if (projCtx.getResourceShadowDiscriminator() != null - && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { - // Don't mess with links for higher-order contexts. The link should - // be dealt with - // during processing of zero-order context. - return; - } - - String projOid = projCtx.getOid(); - if (projOid == null) { - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - // This seems to be OK. In quite a strange way, but still OK. - return; - } - LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump()); - throw new IllegalStateException("Projection " + projCtx.toHumanReadableString() + " has null OID, this should not happen"); - } - - if (linkShouldExist(projCtx, shadowAfterModification, result)) { - // Link should exist - PrismObject objectCurrent = focusContext.getObjectCurrent(); - if (objectCurrent != null) { - for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) { - if (projOid.equals(linkRef.getOid())) { - // Already linked, nothing to do, only be sure, the situation is set with the good value - LOGGER.trace("Updating situation in already linked shadow."); - updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); - return; - } - } - } - // Not linked, need to link - linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result); - // be sure, that the situation is set correctly - LOGGER.trace("Updating situation after shadow was linked."); - updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); - } else { - // Link should NOT exist - if (!focusContext.isDelete()) { - PrismObject objectCurrent = focusContext.getObjectCurrent(); - // it is possible that objectCurrent is null (and objectNew is - // non-null), in case of User ADD operation (MID-2176) - if (objectCurrent != null) { - PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF); - if (linkRef != null) { - for (PrismReferenceValue linkRefVal : linkRef.getValues()) { - if (linkRefVal.getOid().equals(projOid)) { - // Linked, need to unlink - unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx, task, result); - } - } - } - } - } - - // This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow. - LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid); - updateSituationInShadow(task, null, context, focusObjectContext, projCtx, result); - // Not linked, that's OK - } - } - - private boolean linkShouldExist(LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { - if (!projCtx.isShadowExistsInRepo()) { - // Nothing to link to - return false; - } - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { - return false; - } - if (isEmptyThombstone(projCtx)) { - return false; - } - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE - || projCtx.isDelete()) { - return shadowAfterModification != null; - } - if (projCtx.hasPendingOperations()) { - return true; - } - return true; - } - - /** - * Return true if this projection is just a linkRef that points to no - * shadow. - */ - private boolean isEmptyThombstone(LensProjectionContext projCtx) { - return projCtx.getResourceShadowDiscriminator() != null - && projCtx.getResourceShadowDiscriminator().isTombstone() - && projCtx.getObjectCurrent() == null; - } - - private void linkShadow(String userOid, String shadowOid, - LensElementContext focusContext, LensProjectionContext projCtx, Task task, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - - Class typeClass = focusContext.getObjectTypeClass(); - if (!FocusType.class.isAssignableFrom(typeClass)) { - return; - } - - String channel = focusContext.getLensContext().getChannel(); - - LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid); - OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT); - PrismReferenceValue linkRef = prismContext.itemFactory().createReferenceValue(); - linkRef.setOid(shadowOid); - linkRef.setTargetType(ShadowType.COMPLEX_TYPE); - Collection extends ItemDelta> linkRefDeltas = prismContext.deltaFactory().reference() - .createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef); - - try { - cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result); - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, - ChangeType.MODIFY, channel, null); - } catch (ObjectAlreadyExistsException ex) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, - ChangeType.MODIFY, channel, ex); - throw new SystemException(ex); - } catch (Throwable t) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, - ChangeType.MODIFY, channel, t); - throw t; - } finally { - result.computeStatus(); - ObjectDelta userDelta = prismContext.deltaFactory().object().createModifyDelta(userOid, linkRefDeltas, typeClass - ); - LensObjectDeltaOperation userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result, - focusContext, projCtx); - focusContext.addToExecutedDeltas(userDeltaOp); - } - - } - - private PrismObjectDefinition getUserDefinition() { - return userDefinition; - } - - private void unlinkShadow(String focusOid, PrismReferenceValue accountRef, - LensElementContext focusContext, LensProjectionContext projCtx, Task task, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - - Class typeClass = focusContext.getObjectTypeClass(); - if (!FocusType.class.isAssignableFrom(typeClass)) { - return; - } - - String channel = focusContext.getLensContext().getChannel(); - - LOGGER.debug("Unlinking shadow {} from focus {}", accountRef.getOid(), focusOid); - OperationResult result = parentResult.createSubresult(OPERATION_UNLINK_ACCOUNT); - Collection extends ItemDelta> accountRefDeltas = prismContext.deltaFactory().reference().createModificationDeleteCollection( - FocusType.F_LINK_REF, getUserDefinition(), accountRef.clone()); - - try { - cacheRepositoryService.modifyObject(typeClass, focusOid, accountRefDeltas, result); - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid, - ChangeType.MODIFY, channel, null); - } catch (ObjectAlreadyExistsException ex) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid, - ChangeType.MODIFY, channel, ex); - result.recordFatalError(ex); - throw new SystemException(ex); - } catch (Throwable t) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid, - ChangeType.MODIFY, channel, t); - throw t; - } finally { - result.computeStatus(); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(focusOid, accountRefDeltas, typeClass - ); - LensObjectDeltaOperation userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result, - focusContext, projCtx); - focusContext.addToExecutedDeltas(userDeltaOp); - } - - } - - private void updateSituationInShadow(Task task, SynchronizationSituationType newSituation, - LensContext> context, LensFocusContext focusContext, LensProjectionContext projectionCtx, - OperationResult parentResult) throws SchemaException { - - String projectionOid = projectionCtx.getOid(); - - OperationResult result = parentResult.createMinorSubresult(OPERATION_UPDATE_SITUATION_IN_SHADOW); - result.addArbitraryObjectAsParam("situation", newSituation); - result.addParam("accountRef", projectionOid); - - projectionCtx.setSynchronizationSituationResolved(newSituation); - - PrismObject currentShadow; - GetOperationOptions getOptions = GetOperationOptions.createNoFetch(); - getOptions.setAllowNotFound(true); - try { - // TODO consider skipping this operation - at least in some cases - currentShadow = provisioning.getObject(ShadowType.class, projectionOid, - SelectorOptions.createCollection(getOptions), task, result); - } catch (ObjectNotFoundException ex) { - LOGGER.trace("Shadow is gone, skipping modifying situation in shadow."); - result.recordSuccess(); - return; - } catch (Exception ex) { - LOGGER.trace("Problem with getting shadow, skipping modifying situation in shadow."); - result.recordPartialError(ex); - return; - } - - SynchronizationSituationType currentSynchronizationSituation = currentShadow.asObjectable().getSynchronizationSituation(); - if (currentSynchronizationSituation == SynchronizationSituationType.DELETED && ShadowUtil.isDead(currentShadow.asObjectable())) { - LOGGER.trace("Skipping update of synchronization situation for deleted dead shadow"); - result.recordSuccess(); - return; - } - - InternalsConfigurationType internalsConfig = context.getInternalsConfiguration(); - boolean cansSkip = - internalsConfig != null && internalsConfig.getSynchronizationSituationUpdating() != null && - Boolean.TRUE.equals(internalsConfig.getSynchronizationSituationUpdating().isSkipWhenNoChange()); - if (cansSkip) { - if (newSituation == currentSynchronizationSituation) { - LOGGER.trace("Skipping update of synchronization situation because there is no change ({})", - currentSynchronizationSituation); - result.recordSuccess(); - return; - } else { - LOGGER.trace("Updating synchronization situation {} -> {}", currentSynchronizationSituation, newSituation); - } - } - - XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); - List> syncSituationDeltas = SynchronizationUtils - .createSynchronizationSituationAndDescriptionDelta(currentShadow, newSituation, task.getChannel(), - projectionCtx.hasFullShadow(), now, prismContext); - - try { - ModelImplUtils.setRequestee(task, focusContext); - ProvisioningOperationOptions options = ProvisioningOperationOptions.createCompletePostponed(false); - options.setDoNotDiscovery(true); - provisioning.modifyObject(ShadowType.class, projectionOid, syncSituationDeltas, null, options, task, result); - LOGGER.trace("Situation in projection {} was updated to {}.", projectionCtx, newSituation); - } catch (ObjectNotFoundException ex) { - // if the object not found exception is thrown, it's ok..probably - // the account was deleted by previous execution of changes..just - // log in the trace the message for the user.. - LOGGER.debug( - "Situation in account could not be updated. Account not found on the resource. Skipping modifying situation in account"); - return; - } catch (Exception ex) { - result.recordFatalError(ex); - throw new SystemException(ex.getMessage(), ex); - } finally { - ModelImplUtils.clearRequestee(task); - } - // if everything is OK, add result of the situation modification to the - // parent result - result.recordSuccess(); - } - - /** - * @return Returns estimate of the object after modification. Or null if the object was deleted. - * NOTE: this is only partially implemented (only for shadow delete). - */ - private PrismObject executeDelta(ObjectDelta objectDelta, - LensElementContext objectContext, LensContext context, ModelExecuteOptions options, - ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult parentResult) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, - PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - if (objectDelta == null) { - throw new IllegalArgumentException("Null change"); - } - - if (objectDelta.getOid() == null) { - objectDelta.setOid(objectContext.getOid()); - } - - objectDelta = computeDeltaToExecute(objectDelta, objectContext); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("computeDeltaToExecute returned:\n{}", - objectDelta != null ? objectDelta.debugDump(1) : "(null)"); - } - - if (objectDelta == null || objectDelta.isEmpty()) { - LOGGER.debug("Skipping execution of delta because it was already executed: {}", objectContext); - return objectContext.getObjectCurrent(); - } - - if (InternalsConfig.consistencyChecks) { - objectDelta.checkConsistence(ConsistencyCheckScope.fromBoolean(consistencyChecks)); - } - - // Other types than focus types may not be definition-complete (e.g. - // accounts and resources are completed in provisioning) - if (FocusType.class.isAssignableFrom(objectDelta.getObjectTypeClass())) { - objectDelta.assertDefinitions(); - } - - LensUtil.setDeltaOldValue(objectContext, objectDelta); - - if (LOGGER.isTraceEnabled()) { - logDeltaExecution(objectDelta, context, resource, null, task); - } - - OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE_DELTA); - PrismObject objectAfterModification = null; - - try { - if (objectDelta.getChangeType() == ChangeType.ADD) { - objectAfterModification = executeAddition(objectDelta, context, objectContext, options, resource, task, result); - } else if (objectDelta.getChangeType() == ChangeType.MODIFY) { - executeModification(objectDelta, context, objectContext, options, conflictResolution, resource, task, result); - } else if (objectDelta.getChangeType() == ChangeType.DELETE) { - objectAfterModification = executeDeletion(objectDelta, context, objectContext, options, resource, task, result); - } - - // To make sure that the OID is set (e.g. after ADD operation) - LensUtil.setContextOid(context, objectContext, objectDelta.getOid()); - - } finally { - - result.computeStatus(); - if (objectContext != null) { - if (!objectDelta.hasCompleteDefinition()) { - throw new SchemaException("object delta does not have complete definition"); - } - LensObjectDeltaOperation objectDeltaOp = LensUtil.createObjectDeltaOperation( - objectDelta.clone(), result, objectContext, null, resource); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Recording executed delta:\n{}", objectDeltaOp.shorterDebugDump(1)); - } - objectContext.addToExecutedDeltas(objectDeltaOp); - if (result.isTracingNormal(ModelExecuteDeltaTraceType.class)) { - TraceType trace = new ModelExecuteDeltaTraceType(prismContext) - .delta(objectDeltaOp.clone().toLensObjectDeltaOperationType()); // todo kill operation result? - result.addTrace(trace); - } - } else { - if (result.isTracingNormal(ModelExecuteDeltaTraceType.class)) { - LensObjectDeltaOperation objectDeltaOp = new LensObjectDeltaOperation<>(objectDelta); // todo - TraceType trace = new ModelExecuteDeltaTraceType(prismContext) - .delta(objectDeltaOp.toLensObjectDeltaOperationType()); - result.addTrace(trace); - } - } - - if (LOGGER.isDebugEnabled()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("EXECUTION result {}", result.getLastSubresult()); - } else { - // Execution of deltas was not logged yet - logDeltaExecution(objectDelta, context, resource, result.getLastSubresult(), task); - } - } - } - - return objectAfterModification; - } - - private void removeExecutedItemDeltas( - ObjectDelta objectDelta, LensElementContext objectContext) { - if (objectContext == null) { - return; - } - - if (objectDelta == null || objectDelta.isEmpty()) { - return; - } - - if (objectDelta.getModifications() == null || objectDelta.getModifications().isEmpty()) { - return; - } - - List> executedDeltas = objectContext.getExecutedDeltas(); - for (LensObjectDeltaOperation executedDelta : executedDeltas) { - ObjectDelta executed = executedDelta.getObjectDelta(); - Iterator extends ItemDelta> objectDeltaIterator = objectDelta.getModifications().iterator(); - while (objectDeltaIterator.hasNext()) { - ItemDelta d = objectDeltaIterator.next(); - if (executed.containsModification(d, EquivalenceStrategy.LITERAL_IGNORE_METADATA) || d.isEmpty()) { // todo why literal? - objectDeltaIterator.remove(); - } - } - } - } - -// // TODO beware - what if the delta was executed but not successfully? -// private boolean alreadyExecuted(ObjectDelta objectDelta, -// LensElementContext objectContext) { -// if (objectContext == null) { -// return false; -// } -// if (LOGGER.isTraceEnabled()) { -// LOGGER.trace("Checking for already executed delta:\n{}\nIn deltas:\n{}", objectDelta.debugDump(), -// DebugUtil.debugDump(objectContext.getExecutedDeltas())); -// } -// return ObjectDeltaOperation.containsDelta(objectContext.getExecutedDeltas(), objectDelta); -// } - - /** - * Was this object already added? (temporary method, should be removed soon) - */ - private boolean wasAdded(List> executedOperations, - String oid) { - for (LensObjectDeltaOperation operation : executedOperations) { - if (operation.getObjectDelta().isAdd() && oid.equals(operation.getObjectDelta().getOid()) - && !operation.getExecutionResult().isFatalError()) { - return true; - } - } - return false; - } - - /** - * Computes delta to execute, given a list of already executes deltas. See - * below. - */ - private ObjectDelta computeDeltaToExecute(ObjectDelta objectDelta, - LensElementContext objectContext) throws SchemaException { - if (objectContext == null) { - return objectDelta; - } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Computing delta to execute from delta:\n{}\nGiven these executed deltas:\n{}", - objectDelta.debugDump(1), LensObjectDeltaOperation.shorterDebugDump(objectContext.getExecutedDeltas(), 1)); - } - List> executedDeltas = objectContext.getExecutedDeltas(); - ObjectDelta diffDelta = computeDiffDelta(executedDeltas, objectDelta); - - // One more check: is the computed delta idempotent related to objectCurrent? - // Currently we deal only with focusContext because of safety; and also because this check is a reaction - // in change to focus context secondary delta swallowing code (MID-5207). - // - // LookupTableType operation optimization is not available here, because it looks like that isRedundant - // does not work reliably for key-based row deletions (MID-5276). - if (diffDelta != null && objectContext instanceof LensFocusContext> && - !objectContext.isOfType(LookupTableType.class) && - diffDelta.isRedundant(objectContext.getObjectCurrent(), false)) { - LOGGER.trace("delta is idempotent related to {}", objectContext.getObjectCurrent()); - return null; - } - return diffDelta; - } - - /** - * Compute a "difference delta" - given that executedDeltas were executed, - * and objectDelta is about to be executed; eliminates parts that have - * already been done. It is meant as a kind of optimization (for MODIFY - * deltas) and error avoidance (for ADD deltas). - * - * Explanation for ADD deltas: there are situations where an execution wave - * is restarted - when unexpected AlreadyExistsException is reported from - * provisioning. However, in such cases, duplicate ADD Focus deltas were - * generated. So we (TEMPORARILY!) decided to filter them out here. - * - * Unfortunately, this mechanism is not well-defined, and seems to work more - * "by accident" than "by design". It should be replaced with something more - * serious. Perhaps by re-reading current focus state when repeating a wave? - * Actually, it is a supplement for rewriting ADD->MODIFY deltas in - * LensElementContext.getFixedPrimaryDelta. That method converts primary - * deltas (and as far as I know, that is the only place where this problem - * should occur). Nevertheless, for historical and safety reasons I keep - * also the processing in this method. - * - * Anyway, currently it treats only three cases: - * 1) if the objectDelta is present in the list of executed deltas - * 2) if the objectDelta is ADD, and another ADD delta is there (then the difference is computed) - * 3) if objectDelta is MODIFY or DELETE and previous delta was MODIFY - */ - private ObjectDelta computeDiffDelta( - List extends ObjectDeltaOperation> executedDeltas, ObjectDelta objectDelta) { - if (executedDeltas == null || executedDeltas.isEmpty()) { - return objectDelta; - } - - // any delta related to our OID, not ending with fatal error - ObjectDeltaOperation lastRelated = findLastRelatedDelta(executedDeltas, objectDelta); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("findLastRelatedDelta returned:\n{}", - lastRelated != null ? lastRelated.shorterDebugDump(1) : " (null)"); - } - if (lastRelated == null) { - return objectDelta; // nothing found, let us apply our delta - } - if (lastRelated.getExecutionResult().isSuccess() && lastRelated.containsDelta(objectDelta)) { - return null; // case 1 - exact match found with SUCCESS result, - // let's skip the processing of our delta - } - if (!objectDelta.isAdd()) { - if (lastRelated.getObjectDelta().isDelete()) { - return null; // case 3 - } else { - return objectDelta; // MODIFY or DELETE delta after ADD or MODIFY delta - we may safely apply it - } - } - // determine if we got case 2 - if (lastRelated.getObjectDelta().isDelete()) { - return objectDelta; // we can (and should) apply the ADD delta as a - // whole, because the object was deleted - } - // let us treat the most simple case here - meaning we have existing ADD - // delta and nothing more - // TODO add more sophistication if needed - if (!lastRelated.getObjectDelta().isAdd()) { - return objectDelta; // this will probably fail, but ... - } - // at this point we know that ADD was more-or-less successfully - // executed, so let's compute the difference, creating a MODIFY delta - PrismObject alreadyAdded = lastRelated.getObjectDelta().getObjectToAdd(); - PrismObject toBeAddedNow = objectDelta.getObjectToAdd(); - return alreadyAdded.diff(toBeAddedNow); - } - - private ObjectDeltaOperation findLastRelatedDelta( - List extends ObjectDeltaOperation> executedDeltas, ObjectDelta objectDelta) { - for (int i = executedDeltas.size() - 1; i >= 0; i--) { - ObjectDeltaOperation currentOdo = executedDeltas.get(i); - if (currentOdo.getExecutionResult().isFatalError()) { - continue; - } - ObjectDelta current = currentOdo.getObjectDelta(); - - if (current.equals(objectDelta)) { - return currentOdo; - } - - String oid1 = current.isAdd() ? current.getObjectToAdd().getOid() : current.getOid(); - String oid2 = objectDelta.isAdd() ? objectDelta.getObjectToAdd().getOid() - : objectDelta.getOid(); - if (oid1 != null && oid2 != null) { - if (oid1.equals(oid2)) { - return currentOdo; - } - continue; - } - // ADD-MODIFY and ADD-DELETE combinations lead to applying whole - // delta (as a result of computeDiffDelta) - // so we can be lazy and check only ADD-ADD combinations here... - if (!current.isAdd() || !objectDelta.isAdd()) { - continue; - } - // we simply check the type (for focus objects) and - // resource+kind+intent (for shadows) - PrismObject currentObject = current.getObjectToAdd(); - PrismObject objectTypeToAdd = objectDelta.getObjectToAdd(); - Class currentObjectClass = currentObject.getCompileTimeClass(); - Class objectTypeToAddClass = objectTypeToAdd.getCompileTimeClass(); - if (currentObjectClass == null || !currentObjectClass.equals(objectTypeToAddClass)) { - continue; - } - if (FocusType.class.isAssignableFrom(currentObjectClass)) { - return currentOdo; // we suppose there is only one delta of - // Focus class - } - } - return null; - } - - private ProvisioningOperationOptions copyFromModelOptions(ModelExecuteOptions options) { - ProvisioningOperationOptions provisioningOptions = new ProvisioningOperationOptions(); - if (options == null) { - return provisioningOptions; - } - - provisioningOptions.setForce(options.getForce()); - provisioningOptions.setOverwrite(options.getOverwrite()); - return provisioningOptions; - } - - private ProvisioningOperationOptions getProvisioningOptions(LensContext context, - ModelExecuteOptions modelOptions, PrismObject existingShadow, ObjectDelta delta) throws SecurityViolationException { - if (modelOptions == null && context != null) { - modelOptions = context.getOptions(); - } - ProvisioningOperationOptions provisioningOptions = copyFromModelOptions(modelOptions); - - if (executeAsSelf(context, modelOptions, existingShadow, delta)) { - LOGGER.trace("Setting 'execute as self' provisioning option for {}", existingShadow); - provisioningOptions.setRunAsAccountOid(existingShadow.getOid()); - } - - if (context != null && context.getChannel() != null) { - - if (context.getChannel().equals(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_RECON))) { - // TODO: this is probably wrong. We should not have special case - // for recon channel! This should be handled by the provisioning task - // setting the right options there. - provisioningOptions.setCompletePostponed(false); - } - - if (context.getChannel().equals(SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI)) { - // We want to avoid endless loops in error handling. - provisioningOptions.setDoNotDiscovery(true); - } - } - - return provisioningOptions; - } - - // This is a bit of black magic. We only want to execute as self if there a user is changing its own password - // and we also have old password value. - // Later, this should be improved. Maybe we need special model operation option for this? Or maybe it should be somehow - // automatically detected based on resource capabilities? We do not know yet. Therefore let's do the simplest possible - // thing. Otherwise we might do something that we will later regret. - private boolean executeAsSelf(LensContext context, - ModelExecuteOptions modelOptions, PrismObject existingShadow, ObjectDelta delta) throws SecurityViolationException { - if (existingShadow == null) { - return false; - } - - if (!SchemaConstants.CHANNEL_GUI_SELF_SERVICE_URI.equals(context.getChannel())) { - return false; - } - - if (delta == null) { - return false; - } - if (!delta.isModify()) { - return false; - } - PropertyDelta passwordDelta = delta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - if (passwordDelta == null) { - return false; - } - if (passwordDelta.getEstimatedOldValues() == null || passwordDelta.getEstimatedOldValues().isEmpty()) { - return false; - } - ProtectedStringType oldPassword = passwordDelta.getEstimatedOldValues().iterator().next().getValue(); - if (!oldPassword.canGetCleartext()) { - return false; - } - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return false; - } - if (!focusContext.represents(UserType.class)) { - return false; - } - - MidPointPrincipal principal = securityContextManager.getPrincipal(); - if (principal == null) { - return false; - } - FocusType loggedInUser = principal.getFocus(); - - if (!loggedInUser.getOid().equals(focusContext.getOid())) { - return false; - } - return true; - } - - private void logDeltaExecution(ObjectDelta objectDelta, - LensContext context, ResourceType resource, OperationResult result, Task task) { - StringBuilder sb = new StringBuilder(); - sb.append("---[ "); - if (result == null) { - sb.append("Going to EXECUTE"); - } else { - sb.append("EXECUTED"); - } - sb.append(" delta of ").append(objectDelta.getObjectTypeClass().getSimpleName()); - sb.append(" ]---------------------\n"); - DebugUtil.debugDumpLabel(sb, "Channel", 0); - sb.append(" ").append(LensUtil.getChannel(context, task)).append("\n"); - if (context != null) { - DebugUtil.debugDumpLabel(sb, "Wave", 0); - sb.append(" ").append(context.getExecutionWave()).append("\n"); - } - if (resource != null) { - sb.append("Resource: ").append(resource.toString()).append("\n"); - } - sb.append(objectDelta.debugDump()); - sb.append("\n"); - if (result != null) { - DebugUtil.debugDumpLabel(sb, "Result", 0); - sb.append(" ").append(result.getStatus()).append(": ").append(result.getMessage()); - } - sb.append("\n--------------------------------------------------"); - - LOGGER.debug("\n{}", sb); - } - - private OwnerResolver createOwnerResolver(final LensContext context, Task task, - OperationResult result) { - return new LensOwnerResolver<>(context, objectResolver, task, result); - } - - private PrismObject executeAddition(ObjectDelta change, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - PrismObject objectToAdd = change.getObjectToAdd(); - - for (ItemDelta delta : change.getModifications()) { - delta.applyTo(objectToAdd); - } - change.getModifications().clear(); - - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - T objectTypeToAdd = objectToAdd.asObjectable(); - try { - securityEnforcer.authorize(ModelAuthorizationAction.ADD.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectAdd(objectToAdd), ownerResolver, task, result); - - metadataManager.applyMetadataAdd(context, objectToAdd, clock.currentTimeXMLGregorianCalendar(), task, result); - - if (options == null) { - options = context.getOptions(); - } - - RepoAddOptions addOpt = new RepoAddOptions(); - if (ModelExecuteOptions.isOverwrite(options)) { - addOpt.setOverwrite(true); - } - if (ModelExecuteOptions.isNoCrypt(options)) { - addOpt.setAllowUnencryptedValues(true); - } - - String oid; - if (objectTypeToAdd instanceof TaskType) { - oid = addTask((TaskType) objectTypeToAdd, addOpt, result); - } else if (objectTypeToAdd instanceof NodeType) { - throw new UnsupportedOperationException("NodeType cannot be added using model interface"); - } else if (ObjectTypes.isManagedByProvisioning(objectTypeToAdd)) { - - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) change); - - oid = addProvisioningObject(objectToAdd, context, objectContext, provisioningOptions, - resource, task, result); - if (oid == null) { - throw new SystemException( - "Provisioning addObject returned null OID while adding " + objectToAdd); - } - result.addReturn("createdAccountOid", oid); - } else { - FocusConstraintsChecker.clearCacheFor(objectToAdd.asObjectable().getName()); - - oid = cacheRepositoryService.addObject(objectToAdd, addOpt, result); - if (oid == null) { - throw new SystemException("Repository addObject returned null OID while adding " + objectToAdd); - } - } - if (!change.isImmutable()) { - change.setOid(oid); - } - objectToAdd.setOid(oid); - task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), oid, - ChangeType.ADD, context.getChannel(), null); - return objectToAdd; - } catch (Throwable t) { - task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), null, - ChangeType.ADD, context.getChannel(), t); - if (objectTypeToAdd instanceof ShadowType) { - handleProvisioningError(resource, t, task, result); - ((LensProjectionContext) objectContext).setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - return null; - } - throw t; - - } - } - - private void handleProvisioningError(ResourceType resource, Throwable t, Task task, OperationResult result) throws ObjectNotFoundException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException, CommunicationException, SchemaException { - ErrorSelectorType errorSelectorType = ResourceTypeUtil.getConnectorErrorCriticality(resource); - CriticalityType criticality = ExceptionUtil.getCriticality(errorSelectorType, t, CriticalityType.FATAL); - RepoCommonUtils.processErrorCriticality(task, criticality, t, result); - if (CriticalityType.IGNORE == criticality) { - result.muteLastSubresultError(); - } - } - - private PrismObject executeDeletion(ObjectDelta change, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - ObjectAlreadyExistsException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - String oid = change.getOid(); - Class objectTypeClass = change.getObjectTypeClass(); - - PrismObject objectOld = objectContext.getObjectOld(); - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - PrismObject objectAfterModification = null; - try { - securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelete(objectOld), ownerResolver, task, result); - - if (TaskType.class.isAssignableFrom(objectTypeClass)) { - taskManager.deleteTask(oid, result); - } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { - taskManager.deleteNode(oid, result); - } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) change); - try { - objectAfterModification = deleteProvisioningObject(objectTypeClass, oid, context, objectContext, - provisioningOptions, resource, task, result); - } catch (ObjectNotFoundException e) { - // Object that we wanted to delete is already gone. This can - // happen in some race conditions. - // As the resulting state is the same as we wanted it to be - // we will not complain and we will go on. - LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid, - objectTypeClass); - result.muteLastSubresultError(); - } - } else { - try { - cacheRepositoryService.deleteObject(objectTypeClass, oid, result); - } catch (ObjectNotFoundException e) { - // Object that we wanted to delete is already gone. This can - // happen in some race conditions. - // As the resulting state is the same as we wanted it to be - // we will not complain and we will go on. - LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid, - objectTypeClass); - result.muteLastSubresultError(); - } - } - task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE, - context.getChannel(), null); - } catch (Throwable t) { - task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE, - context.getChannel(), t); - - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - handleProvisioningError(resource, t, task, result); - return objectContext.getObjectCurrent(); - } - - throw t; - } - - return objectAfterModification; - } - - private void executeModification(ObjectDelta delta, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, CommunicationException, - ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - Class objectTypeClass = delta.getObjectTypeClass(); - - // We need current object here. The current object is used to get data for id-only container delete deltas, - // replace deltas and so on. The authorization code can figure out new object if needed, but it needs - // current object to start from. - // We cannot use old object here. That would fail in multi-wave executions. We want object that has all the previous - // wave changes already applied. - PrismObject baseObject = objectContext.getObjectCurrent(); - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - try { - securityEnforcer.authorize(ModelAuthorizationAction.MODIFY.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelta(baseObject, delta), ownerResolver, task, result); - - if (shouldApplyModifyMetadata(objectTypeClass, context.getSystemConfigurationType())) { - metadataManager.applyMetadataModify(delta, objectContext, objectTypeClass, - clock.currentTimeXMLGregorianCalendar(), task, context, result); - } - - if (delta.isEmpty()) { - // Nothing to do - return; - } - - if (TaskType.class.isAssignableFrom(objectTypeClass)) { - taskManager.modifyTask(delta.getOid(), delta.getModifications(), result); - } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { - throw new UnsupportedOperationException("NodeType is not modifiable using model interface"); - } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) delta); - String oid = modifyProvisioningObject(objectTypeClass, delta.getOid(), - delta.getModifications(), context, objectContext, provisioningOptions, resource, - task, result); - if (!oid.equals(delta.getOid())) { - delta.setOid(oid); - } - } else { - FocusConstraintsChecker.clearCacheForDelta(delta.getModifications()); - ModificationPrecondition precondition = null; - if (conflictResolution != null) { - String readVersion = objectContext.getObjectReadVersion(); - if (readVersion != null) { - LOGGER.trace("Modification with precondition, readVersion={}", readVersion); - precondition = new VersionPrecondition<>(readVersion); - } else { - LOGGER.warn("Requested careful modification of {}, but there is no read version", objectContext.getHumanReadableName()); - } - } - cacheRepositoryService.modifyObject(objectTypeClass, delta.getOid(), - delta.getModifications(), precondition, null, result); - } - task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY, - context.getChannel(), null); - } catch (Throwable t) { - task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY, - context.getChannel(), t); - throw t; - } - } - - private boolean shouldApplyModifyMetadata(Class objectTypeClass, SystemConfigurationType config) { - if (!ShadowType.class.equals(objectTypeClass)) { - return true; - } else if (config == null || config.getInternals() == null || config.getInternals().getShadowMetadataRecording() == null) { - return true; - } else { - MetadataRecordingStrategyType recording = config.getInternals().getShadowMetadataRecording(); - return !Boolean.TRUE.equals(recording.isSkipOnModify()); - } - } - - private String addTask(TaskType task, RepoAddOptions addOpt, OperationResult result) - throws ObjectAlreadyExistsException { - try { - return taskManager.addTask(task.asPrismObject(), addOpt, result); - } catch (ObjectAlreadyExistsException ex) { - throw ex; - } catch (Exception ex) { - LoggingUtils.logException(LOGGER, "Couldn't add object {} to task manager", ex, task.getName()); - throw new SystemException(ex.getMessage(), ex); - } - } - - private String addProvisioningObject(PrismObject object, - LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - ObjectAlreadyExistsException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException { - - if (object.canRepresent(ShadowType.class)) { - ShadowType shadow = (ShadowType) object.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadow); - if (resourceOid == null) { - throw new IllegalArgumentException("Resource OID is null in shadow"); - } - } - - OperationProvisioningScriptsType scripts = null; - if (object.canRepresent(ShadowType.class)) { - scripts = prepareScripts(object, context, objectContext, ProvisioningOperationTypeType.ADD, - resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - String oid = provisioning.addObject(object, scripts, options, task, result); - ModelImplUtils.clearRequestee(task); - return oid; - } - - private PrismObject deleteProvisioningObject( - Class objectTypeClass, String oid, LensContext context, LensElementContext objectContext, - ProvisioningOperationOptions options, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, PolicyViolationException { - - PrismObject shadowToModify = null; - OperationProvisioningScriptsType scripts = null; - try { - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - shadowToModify = provisioning.getObject(objectTypeClass, oid, - SelectorOptions.createCollection(rootOpts), task, result); - } catch (ObjectNotFoundException ex) { - // this is almost OK, mute the error and try to delete account (it - // will fail if something is wrong) - result.muteLastSubresultError(); - } - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - scripts = prepareScripts(shadowToModify, context, objectContext, - ProvisioningOperationTypeType.DELETE, resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - PrismObject objectAfterModification = provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result); - ModelImplUtils.clearRequestee(task); - return objectAfterModification; - } - - private String modifyProvisioningObject( - Class objectTypeClass, String oid, Collection extends ItemDelta> modifications, - LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - - PrismObject shadowToModify = null; - OperationProvisioningScriptsType scripts = null; - try { - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - shadowToModify = provisioning.getObject(objectTypeClass, oid, - SelectorOptions.createCollection(rootOpts), task, result); - } catch (ObjectNotFoundException e) { - // We do not want the operation to fail here. The object might have - // been re-created on the resource - // or discovery might re-create it. So simply ignore this error and - // give provisioning a chance to fail - // properly. - result.muteLastSubresultError(); - LOGGER.warn("Repository object {}: {} is gone. But trying to modify resource object anyway", - objectTypeClass, oid); - } - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - scripts = prepareScripts(shadowToModify, context, objectContext, - ProvisioningOperationTypeType.MODIFY, resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - String changedOid = provisioning.modifyObject(objectTypeClass, oid, modifications, scripts, options, - task, result); - ModelImplUtils.clearRequestee(task); - return changedOid; - } - - private OperationProvisioningScriptsType prepareScripts( - PrismObject changedObject, LensContext context, LensElementContext objectContext, - ProvisioningOperationTypeType operation, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing scripts."); - return null; - } - OperationProvisioningScriptsType resourceScripts = resource.getScripts(); - PrismObject resourceObject = (PrismObject) changedObject; - - PrismObject user = null; - if (context.getFocusContext() != null) { - if (context.getFocusContext().getObjectNew() != null) { - user = context.getFocusContext().getObjectNew(); - } else if (context.getFocusContext().getObjectCurrent() != null) { - user = context.getFocusContext().getObjectCurrent(); - } else if (context.getFocusContext().getObjectOld() != null) { - user = context.getFocusContext().getObjectOld(); - } - } - - LensProjectionContext projectionCtx = (LensProjectionContext) objectContext; - PrismObject shadow = null; - if (projectionCtx.getObjectNew() != null) { - shadow = projectionCtx.getObjectNew(); - } else if (projectionCtx.getObjectCurrent() != null) { - shadow = projectionCtx.getObjectCurrent(); - } else { - shadow = projectionCtx.getObjectOld(); - } - - if (shadow == null) { - //put at least something - shadow = resourceObject.clone(); - } - - ResourceShadowDiscriminator discr = ((LensProjectionContext) objectContext) - .getResourceShadowDiscriminator(); - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, discr, - resource.asPrismObject(), context.getSystemConfiguration(), objectContext, prismContext); - // Having delta in provisioning scripts may be very useful. E.g. the script can optimize execution of expensive operations. - variables.put(ExpressionConstants.VAR_DELTA, projectionCtx.getDelta(), ObjectDelta.class); - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, (LensProjectionContext) objectContext, task, result)); - try { - return evaluateScript(resourceScripts, discr, operation, null, variables, expressionProfile, context, objectContext, task, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - } - - private OperationProvisioningScriptsType evaluateScript(OperationProvisioningScriptsType resourceScripts, - ResourceShadowDiscriminator discr, ProvisioningOperationTypeType operation, BeforeAfterType order, - ExpressionVariables variables, ExpressionProfile expressionProfile, LensContext> context, - LensElementContext> objectContext, Task task, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - OperationProvisioningScriptsType outScripts = new OperationProvisioningScriptsType(); - - if (resourceScripts != null) { - OperationProvisioningScriptsType scripts = resourceScripts.clone(); - for (OperationProvisioningScriptType script : scripts.getScript()) { - if (discr != null) { - if (script.getKind() != null && !script.getKind().isEmpty() - && !script.getKind().contains(discr.getKind())) { - continue; - } - if (script.getIntent() != null && !script.getIntent().isEmpty() - && !script.getIntent().contains(discr.getIntent()) && discr.getIntent() != null) { - continue; - } - } - if (operation != null) { - if (!script.getOperation().contains(operation)) { - continue; - } - } - if (order != null) { - if (order != null && order != script.getOrder()) { - continue; - } - } - // Let's do the most expensive evaluation last - if (!evaluateScriptCondition(script, variables, expressionProfile, task, result)) { - continue; - } - for (ProvisioningScriptArgumentType argument : script.getArgument()) { - evaluateScriptArgument(argument, variables, context, objectContext, task, result); - } - outScripts.getScript().add(script); - } - } - - return outScripts; - } - - private boolean evaluateScriptCondition(OperationProvisioningScriptType script, - ExpressionVariables variables, ExpressionProfile expressionProfile, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType condition = script.getCondition(); - if (condition == null) { - return true; - } - - PrismPropertyValue conditionOutput = ExpressionUtil.evaluateCondition(variables, condition, expressionProfile, expressionFactory, " condition for provisioning script ", task, result); - if (conditionOutput == null) { - return true; - } - - Boolean conditionOutputValue = conditionOutput.getValue(); - - return BooleanUtils.isNotFalse(conditionOutputValue); - - } - - private void evaluateScriptArgument(ProvisioningScriptArgumentType argument, - ExpressionVariables variables, LensContext> context, - LensElementContext> objectContext, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - - final QName fakeScriptArgumentName = new QName(SchemaConstants.NS_C, "arg"); - - PrismPropertyDefinition scriptArgumentDefinition = prismContext.definitionFactory().createPropertyDefinition( - fakeScriptArgumentName, DOMUtil.XSD_STRING); - - String shortDesc = "Provisioning script argument expression"; - Expression, PrismPropertyDefinition> expression = expressionFactory - .makeExpression(argument, scriptArgumentDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); - ExpressionEnvironment, ?, ?> env = new ExpressionEnvironment<>(context, - objectContext instanceof LensProjectionContext ? (LensProjectionContext) objectContext : null, task, result); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, env, result); - - Collection> nonNegativeValues = null; - if (outputTriple != null) { - nonNegativeValues = outputTriple.getNonNegativeValues(); - } - - // replace dynamic script with static value.. - XNodeFactory factory = prismContext.xnodeFactory(); - - argument.getExpressionEvaluator().clear(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - // We need to create at least one evaluator. Otherwise the - // expression code will complain - JAXBElement el = new JAXBElement<>(SchemaConstants.C_VALUE, RawType.class, new RawType(prismContext)); - argument.getExpressionEvaluator().add(el); - - } else { - for (PrismPropertyValue val : nonNegativeValues) { - PrimitiveXNode prim = factory.primitive(val.getValue(), DOMUtil.XSD_STRING); - JAXBElement el = new JAXBElement<>(SchemaConstants.C_VALUE, RawType.class, new RawType(prim, prismContext)); - argument.getExpressionEvaluator().add(el); - } - } - } - - private void executeReconciliationScript( - LensProjectionContext projContext, LensContext context, BeforeAfterType order, Task task, - OperationResult parentResult) throws SchemaException, ObjectNotFoundException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectAlreadyExistsException { - - if (!projContext.isDoReconciliation()) { - return; - } - - ResourceType resource = projContext.getResource(); - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts."); - return; - } - - OperationProvisioningScriptsType resourceScripts = resource.getScripts(); - if (resourceScripts == null) { - return; - } - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - executeProvisioningScripts(context, projContext, resourceScripts, ProvisioningOperationTypeType.RECONCILE, order, expressionProfile, task, parentResult); - } - - private Object executeProvisioningScripts(LensContext context, LensProjectionContext projContext, - OperationProvisioningScriptsType scripts, ProvisioningOperationTypeType operation, BeforeAfterType order, ExpressionProfile expressionProfile, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException, ObjectAlreadyExistsException { - - ResourceType resource = projContext.getResource(); - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts."); - return null; - } - - PrismObject user = null; - PrismObject shadow = null; - - if (context.getFocusContext() != null) { - if (context.getFocusContext().getObjectNew() != null) { - user = context.getFocusContext().getObjectNew(); - } else if (context.getFocusContext().getObjectOld() != null) { - user = context.getFocusContext().getObjectOld(); - } - // if (order == ProvisioningScriptOrderType.BEFORE) { - // user = context.getFocusContext().getObjectOld(); - // } else if (order == ProvisioningScriptOrderType.AFTER) { - // user = context.getFocusContext().getObjectNew(); - // } else { - // throw new IllegalArgumentException("Unknown order "+order); - // } - } - - if (order == BeforeAfterType.BEFORE) { - shadow = (PrismObject) projContext.getObjectOld(); - } else if (order == BeforeAfterType.AFTER) { - shadow = (PrismObject) projContext.getObjectNew(); - } else { - shadow = (PrismObject) projContext.getObjectCurrent(); - } - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, - projContext.getResourceShadowDiscriminator(), resource.asPrismObject(), - context.getSystemConfiguration(), projContext, prismContext); - Object scriptResult = null; - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, projContext, task, parentResult)); - try { - OperationProvisioningScriptsType evaluatedScript = evaluateScript(scripts, - projContext.getResourceShadowDiscriminator(), operation, order, - variables, expressionProfile, context, projContext, task, parentResult); - for (OperationProvisioningScriptType script : evaluatedScript.getScript()) { - ModelImplUtils.setRequestee(task, context); - scriptResult = provisioning.executeScript(resource.getOid(), script, task, parentResult); - ModelImplUtils.clearRequestee(task); - } - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - return scriptResult; - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.FOCUS_OPERATION; +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.RESOURCE_OBJECT_OPERATION; +import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; +import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerables; +import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.xml.bind.JAXBElement; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import org.apache.commons.lang.BooleanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.common.SynchronizationUtils; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.ProgressInformation; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; +import com.evolveum.midpoint.prism.xnode.XNodeFactory; +import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.api.*; +import com.evolveum.midpoint.repo.common.expression.*; +import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.OwnerResolver; +import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import com.evolveum.prism.xml.ns._public.types_3.RawType; + +/** + * @author semancik + */ +@Component +public class ChangeExecutor { + + private static final Trace LOGGER = TraceManager.getTrace(ChangeExecutor.class); + + private static final String OPERATION_EXECUTE_DELTA = ChangeExecutor.class.getName() + ".executeDelta"; + private static final String OPERATION_EXECUTE = ChangeExecutor.class.getName() + ".execute"; + private static final String OPERATION_EXECUTE_FOCUS = OPERATION_EXECUTE + ".focus"; + private static final String OPERATION_EXECUTE_PROJECTION = OPERATION_EXECUTE + ".projection"; + private static final String OPERATION_LINK_ACCOUNT = ChangeExecutor.class.getName() + ".linkShadow"; + private static final String OPERATION_UNLINK_ACCOUNT = ChangeExecutor.class.getName() + ".unlinkShadow"; + private static final String OPERATION_UPDATE_SITUATION_IN_SHADOW = ChangeExecutor.class.getName() + ".updateSituationInShadow"; + + @Autowired private transient TaskManager taskManager; + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; + @Autowired private ProvisioningService provisioning; + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private SecurityEnforcer securityEnforcer; + @Autowired private SecurityContextManager securityContextManager; + @Autowired private Clock clock; + @Autowired private ModelObjectResolver objectResolver; + @Autowired private OperationalDataManager metadataManager; + @Autowired private CredentialsProcessor credentialsProcessor; + + private PrismObjectDefinition userDefinition = null; + private PrismObjectDefinition shadowDefinition = null; + + @PostConstruct + private void locateDefinitions() { + userDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(UserType.class); + shadowDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(ShadowType.class); + } + + // returns true if current operation has to be restarted, see + // ObjectAlreadyExistsException handling (TODO specify more exactly) + public boolean executeChanges(LensContext context, Task task, + OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException, PreconditionViolationException, PolicyViolationException { + + OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE); + + try { + + // FOCUS + + context.checkAbortRequested(); + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + ObjectDelta focusDelta = focusContext.getWaveExecutableDelta(context.getExecutionWave()); + + focusDelta = applyPendingObjectPolicyStateModifications(focusContext, focusDelta); + focusDelta = applyPendingAssignmentPolicyStateModifications(focusContext, focusDelta); + + if (focusDelta == null && !context.hasProjectionChange()) { + LOGGER.trace("Skipping focus change execute, because user delta is null"); + } else { + + if (focusDelta == null) { + focusDelta = focusContext.getObjectAny().createModifyDelta(); + } + + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + applyObjectPolicy(focusContext, focusDelta, archetypePolicy); + + OperationResult subResult = result.createSubresult( + OPERATION_EXECUTE_FOCUS + "." + focusContext.getObjectTypeClass().getSimpleName()); + + try { + // Will remove credential deltas or hash them + focusDelta = credentialsProcessor.transformFocusExecutionDelta(context, focusDelta); + } catch (EncryptionException e) { + recordFatalError(subResult, result, null, e); + result.computeStatus(); + throw new SystemException(e.getMessage(), e); + } + + applyLastProvisioningTimestamp(context, focusDelta); + + try { + + context.reportProgress(new ProgressInformation(FOCUS_OPERATION, ENTERING)); + + ConflictResolutionType conflictResolution = ModelExecuteOptions + .getFocusConflictResolution(context.getOptions()); + + executeDelta(focusDelta, focusContext, context, null, conflictResolution, null, task, subResult); + + if (focusDelta.isAdd() && focusDelta.getOid() != null) { + // The watcher can already exist; if the OID was pre-existing in the object. + if (context.getFocusConflictWatcher() == null) { + ConflictWatcher watcher = context + .createAndRegisterFocusConflictWatcher(focusDelta.getOid(), cacheRepositoryService); + watcher.setExpectedVersion(focusDelta.getObjectToAdd().getVersion()); + } + } + subResult.computeStatus(); + + } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException | RuntimeException e) { + recordFatalError(subResult, result, null, e); + throw e; + + } catch (PreconditionViolationException e) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Modification precondition failed for {}: {}", focusContext.getHumanReadableName(), + e.getMessage()); + } + // TODO: fatal error if the conflict resolution is "error" (later) + result.recordHandledError(e); + throw e; + + } catch (ObjectAlreadyExistsException e) { + subResult.computeStatus(); + if (!subResult.isSuccess() && !subResult.isHandledError()) { + subResult.recordFatalError(e); + } + result.computeStatusComposite(); + throw e; + } finally { + context.reportProgress(new ProgressInformation(FOCUS_OPERATION, subResult)); + } + } + } + + // PROJECTIONS + + context.checkAbortRequested(); + + boolean restartRequested = false; + + for (LensProjectionContext projCtx : context.getProjectionContexts()) { + if (projCtx.getWave() != context.getExecutionWave()) { + LOGGER.trace("Skipping projection context {} because its wave ({}) is different from execution wave ({})", + projCtx.toHumanReadableString(), projCtx.getWave(), context.getExecutionWave()); + continue; + } + + if (!projCtx.isCanProject()) { + LOGGER.trace("Skipping projection context {} because canProject is false", projCtx.toHumanReadableString()); + continue; + } + + // we should not get here, but just to be sure + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { + LOGGER.trace("Skipping ignored projection context {}", projCtx.toHumanReadableString()); + continue; + } + + OperationResult subResult = result.subresult(OPERATION_EXECUTE_PROJECTION + "." + projCtx.getObjectTypeClass().getSimpleName()) + .addParam("resource", projCtx.getResource()) + .addArbitraryObjectAsContext("discriminator", projCtx.getResourceShadowDiscriminator()) + .build(); + + PrismObject shadowAfterModification = null; + try { + LOGGER.trace("Executing projection context {}", projCtx.toHumanReadableString()); + + context.checkAbortRequested(); + + context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, + projCtx.getResourceShadowDiscriminator(), ENTERING)); + + executeReconciliationScript(projCtx, context, BeforeAfterType.BEFORE, task, subResult); + + ObjectDelta projDelta = projCtx.getExecutableDelta(); + + if (shouldBeDeleted(projDelta, projCtx)) { + projDelta = prismContext.deltaFactory().object() + .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); + } + + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + if (context.getFocusContext() != null + && context.getFocusContext().getDelta() != null + && context.getFocusContext().getDelta().isDelete() + && context.getOptions() != null + && ModelExecuteOptions.isForce(context.getOptions())) { + if (projDelta == null) { + projDelta = prismContext.deltaFactory().object() + .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); + } + } + if (projDelta != null && projDelta.isDelete()) { + + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, + subResult); + + } + } else { + + if (projDelta == null || projDelta.isEmpty()) { + LOGGER.trace("No change for {}", projCtx.getResourceShadowDiscriminator()); + shadowAfterModification = projCtx.getObjectCurrent(); + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + // Make sure post-reconcile delta is always executed, + // even if there is no change + executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, + subResult); + + subResult.computeStatus(); + subResult.recordNotApplicableIfUnknown(); + continue; + + } else if (projDelta.isDelete() && projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // HACK ... for higher-order context check if this was + // already deleted + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, + projCtx); + if (lowerOrderContext != null && lowerOrderContext.isDelete()) { + // We assume that this was already executed + subResult.setStatus(OperationResultStatus.NOT_APPLICABLE); + continue; + } + } + + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); + + if (projCtx.isAdd() && shadowAfterModification != null) { + projCtx.setExists(true); + } + + } + + subResult.computeStatus(); + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult); + + subResult.computeStatus(); + subResult.recordNotApplicableIfUnknown(); + + } catch (SchemaException | ObjectNotFoundException | PreconditionViolationException | CommunicationException | + ConfigurationException | SecurityViolationException | PolicyViolationException | ExpressionEvaluationException | RuntimeException | Error e) { + recordProjectionExecutionException(e, projCtx, subResult, SynchronizationPolicyDecision.BROKEN); + + // We still want to update the links here. E.g. this may be live sync case where we discovered new account + // try to reconcile, but the reconciliation fails. We still want this shadow linked to user. + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + ModelImplUtils.handleConnectorErrorCriticality(projCtx.getResource(), e, subResult); + + } catch (ObjectAlreadyExistsException e) { + + // This exception is quite special. We have to decide how bad this really is. + // This may be rename conflict. Which would be bad. + // Or this may be attempt to create account that already exists and just needs + // to be linked. Which is no big deal and consistency mechanism (discovery) will + // easily handle that. In that case it is done in "another task" which is + // quasi-asynchornously executed from provisioning by calling notifyChange. + // Once that is done then the account is already linked. And all we need to do + // is to restart this whole operation. + + // check if this is a repeated attempt - OAEE was not handled + // correctly, e.g. if creating "Users" user in AD, whereas + // "Users" is SAM Account Name which is used by a built-in group + // - in such case, mark the context as broken + if (isRepeatedAlreadyExistsException(projCtx)) { + // This is the bad case. Currently we do not do anything more intelligent than to look for + // repeated error. If we get OAEE twice then this is bad and we thow up. + // TODO: do something smarter here + LOGGER.debug("Repeated ObjectAlreadyExistsException detected, marking projection {} as broken", projCtx.toHumanReadableString()); + recordProjectionExecutionException(e, projCtx, subResult, + SynchronizationPolicyDecision.BROKEN); + continue; + } + + // in his case we do not need to set account context as + // broken, instead we need to restart projector for this + // context to recompute new account or find out if the + // account was already linked.. + // and also do not set fatal error to the operation result, this + // is a special case + // if it is fatal, it will be set later + // but we need to set some result + subResult.recordSuccess(); + restartRequested = true; + LOGGER.debug("ObjectAlreadyExistsException for projection {}, requesting projector restart", projCtx.toHumanReadableString()); + // we will process remaining projections when retrying the wave + break; + + } finally { + context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, + projCtx.getResourceShadowDiscriminator(), subResult)); + } + } + + // Result computation here needs to be slightly different + result.computeStatusComposite(); + return restartRequested; + + } catch (Throwable t) { + result.recordThrowableIfNeeded(t); // last resort: to avoid UNKNOWN subresults + throw t; + } + } + + private ObjectDelta applyPendingObjectPolicyStateModifications(LensFocusContext focusContext, + ObjectDelta focusDelta) throws SchemaException { + for (ItemDelta, ?> itemDelta : focusContext.getPendingObjectPolicyStateModifications()) { + focusDelta = focusContext.swallowToDelta(focusDelta, itemDelta); + } + focusContext.clearPendingObjectPolicyStateModifications(); + return focusDelta; + } + + private ObjectDelta applyPendingAssignmentPolicyStateModifications(LensFocusContext focusContext, ObjectDelta focusDelta) + throws SchemaException { + for (Map.Entry>> entry : focusContext + .getPendingAssignmentPolicyStateModifications().entrySet()) { + PlusMinusZero mode = entry.getKey().mode; + if (mode == PlusMinusZero.MINUS) { + continue; // this assignment is being thrown out anyway, so let's ignore it (at least for now) + } + AssignmentType assignmentToFind = entry.getKey().assignment; + List> modifications = entry.getValue(); + if (modifications.isEmpty()) { + continue; + } + LOGGER.trace("Applying policy state modifications for {} ({}):\n{}", assignmentToFind, mode, + DebugUtil.debugDumpLazily(modifications)); + if (mode == PlusMinusZero.ZERO) { + if (assignmentToFind.getId() == null) { + throw new IllegalStateException("Existing assignment with null id: " + assignmentToFind); + } + for (ItemDelta, ?> modification : modifications) { + focusDelta = focusContext.swallowToDelta(focusDelta, modification); + } + } else { + assert mode == PlusMinusZero.PLUS; + if (focusDelta != null && focusDelta.isAdd()) { + swallowIntoValues(((FocusType) focusDelta.getObjectToAdd().asObjectable()).getAssignment(), + assignmentToFind, modifications); + } else { + ContainerDelta assignmentDelta = focusDelta != null ? + focusDelta.findContainerDelta(FocusType.F_ASSIGNMENT) : null; + if (assignmentDelta == null) { + throw new IllegalStateException( + "We have 'plus' assignment to modify but there's no assignment delta. Assignment=" + + assignmentToFind + ", objectDelta=" + focusDelta); + } + if (assignmentDelta.isReplace()) { + swallowIntoValues(asContainerables(assignmentDelta.getValuesToReplace()), assignmentToFind, + modifications); + } else if (assignmentDelta.isAdd()) { + swallowIntoValues(asContainerables(assignmentDelta.getValuesToAdd()), assignmentToFind, + modifications); + } else { + throw new IllegalStateException( + "We have 'plus' assignment to modify but there're no values to add or replace in assignment delta. Assignment=" + + assignmentToFind + ", objectDelta=" + focusDelta); + } + } + } + } + focusContext.clearPendingAssignmentPolicyStateModifications(); + return focusDelta; + } + + private void swallowIntoValues(Collection assignments, AssignmentType assignmentToFind, List> modifications) + throws SchemaException { + for (AssignmentType assignment : assignments) { + PrismContainerValue> pcv = assignment.asPrismContainerValue(); + PrismContainerValue> pcvToFind = assignmentToFind.asPrismContainerValue(); + if (pcv.representsSameValue(pcvToFind, false) || pcv.equals(pcvToFind, EquivalenceStrategy.REAL_VALUE_CONSIDER_DIFFERENT_IDS)) { + // TODO what if ID of the assignment being added is changed in repo? Hopefully it will be not. + for (ItemDelta, ?> modification : modifications) { + ItemPath newParentPath = modification.getParentPath().rest(2); // killing assignment + ID + ItemDelta, ?> pathRelativeModification = modification.cloneWithChangedParentPath(newParentPath); + pathRelativeModification.applyTo(pcv); + } + return; + } + } + // TODO change to warning + throw new IllegalStateException("We have 'plus' assignment to modify but it couldn't be found in assignment delta. Assignment=" + assignmentToFind + ", new assignments=" + assignments); + } + + private void applyLastProvisioningTimestamp(LensContext context, ObjectDelta focusDelta) throws SchemaException { + if (!context.hasProjectionChange()) { + return; + } + if (focusDelta.isAdd()) { + + PrismObject objectToAdd = focusDelta.getObjectToAdd(); + PrismContainer metadataContainer = objectToAdd.findOrCreateContainer(ObjectType.F_METADATA); + metadataContainer.getRealValue().setLastProvisioningTimestamp(clock.currentTimeXMLGregorianCalendar()); + + } else if (focusDelta.isModify()) { + + PropertyDelta provTimestampDelta = prismContext.deltaFactory().property().createModificationReplaceProperty( + ItemPath.create(ObjectType.F_METADATA, MetadataType.F_LAST_PROVISIONING_TIMESTAMP), + context.getFocusContext().getObjectDefinition(), + clock.currentTimeXMLGregorianCalendar()); + focusDelta.addModification(provTimestampDelta); + + } + } + + private boolean shouldBeDeleted(ObjectDelta accDelta, LensProjectionContext accCtx) { + return (accDelta == null || accDelta.isEmpty()) + && (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || accCtx.getSynchronizationIntent() == SynchronizationIntent.DELETE); + } + + private boolean isRepeatedAlreadyExistsException( + LensProjectionContext projContext) { + int deltas = projContext.getExecutedDeltas().size(); + LOGGER.trace("isRepeatedAlreadyExistsException starting; number of executed deltas = {}", deltas); + if (deltas < 2) { + return false; + } + LensObjectDeltaOperation lastDeltaOp = projContext.getExecutedDeltas().get(deltas - 1); + LensObjectDeltaOperation previousDeltaOp = projContext.getExecutedDeltas() + .get(deltas - 2); + // TODO check also previous execution result to see if it's + // AlreadyExistException? + ObjectDelta lastDelta = lastDeltaOp.getObjectDelta(); + ObjectDelta previousDelta = previousDeltaOp.getObjectDelta(); + boolean rv; + if (lastDelta.isAdd() && previousDelta.isAdd()) { + rv = isEquivalentAddDelta(lastDelta.getObjectToAdd(), previousDelta.getObjectToAdd()); + } else if (lastDelta.isModify() && previousDelta.isModify()) { + rv = isEquivalentModifyDelta(lastDelta.getModifications(), previousDelta.getModifications()); + } else { + rv = false; + } + LOGGER.trace( + "isRepeatedAlreadyExistsException returning {}; based of comparison of previousDelta:\n{}\nwith lastDelta:\n{}", + rv, previousDelta, lastDelta); + return rv; + } + + private boolean isEquivalentModifyDelta(Collection extends ItemDelta, ?>> modifications1, + Collection extends ItemDelta, ?>> modifications2) { + Collection extends ItemDelta, ?>> attrDeltas1 = ItemDeltaCollectionsUtil + .findItemDeltasSubPath(modifications1, ShadowType.F_ATTRIBUTES); + Collection extends ItemDelta, ?>> attrDeltas2 = ItemDeltaCollectionsUtil + .findItemDeltasSubPath(modifications2, ShadowType.F_ATTRIBUTES); + //noinspection unchecked,RedundantCast + return MiscUtil.unorderedCollectionEquals((Collection) attrDeltas1, (Collection) attrDeltas2); + } + + private boolean isEquivalentAddDelta(PrismObject object1, PrismObject object2) { + PrismContainer attributes1 = object1.findContainer(ShadowType.F_ATTRIBUTES); + PrismContainer attributes2 = object2.findContainer(ShadowType.F_ATTRIBUTES); + if (attributes1 == null || attributes2 == null || attributes1.size() != 1 + || attributes2.size() != 1) { // suspicious cases + return false; + } + return attributes1.getValue().equivalent(attributes2.getValue()); + } + + private void applyObjectPolicy(LensFocusContext focusContext, + ObjectDelta focusDelta, ArchetypePolicyType archetypePolicy) { + if (archetypePolicy == null) { + return; + } + PrismObject objectNew = focusContext.getObjectNew(); + if (focusDelta.isAdd() && objectNew.getOid() == null) { + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + processItemConstraint(focusContext, objectNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + processItemConstraint(focusContext, objectNew, itemConstraintType); + } + + } + } + + private void processItemConstraint(LensFocusContext focusContext, PrismObject objectNew, ItemConstraintType itemConstraintType) { + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + PrismProperty prop = objectNew.findProperty(itemPath); + String stringValue = prop.getRealValue().toString(); + focusContext.setOid(stringValue); + } + } + + private void recordProjectionExecutionException(Throwable e, + LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) { + subResult.recordFatalError(e); + LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e); + if (decision != null) { + accCtx.setSynchronizationPolicyDecision(decision); + } + } + + private void recordFatalError(OperationResult subResult, OperationResult result, String message, + Throwable e) { + if (message == null) { + message = e.getMessage(); + } + subResult.recordFatalError(e); + if (result != null) { + result.computeStatusComposite(); + } + } + + /** + * Make sure that the account is linked (or unlinked) as needed. + */ + private void updateLinks(LensContext> context, + LensFocusContext focusObjectContext, LensProjectionContext projCtx, + PrismObject shadowAfterModification, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { + if (focusObjectContext == null) { + return; + } + Class objectTypeClass = focusObjectContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(objectTypeClass)) { + return; + } + //noinspection unchecked + LensFocusContext focusContext = (LensFocusContext) focusObjectContext; + + if (projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // Don't mess with links for higher-order contexts. The link should + // be dealt with + // during processing of zero-order context. + return; + } + + String projOid = projCtx.getOid(); + if (projOid == null) { + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + // This seems to be OK. In quite a strange way, but still OK. + return; + } + LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump()); + throw new IllegalStateException("Projection " + projCtx.toHumanReadableString() + " has null OID, this should not happen"); + } + + if (linkShouldExist(focusContext, projCtx, shadowAfterModification, result)) { + // Link should exist + PrismObject objectCurrent = focusContext.getObjectCurrent(); + if (objectCurrent != null) { + for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) { + if (projOid.equals(linkRef.getOid())) { + // Already linked, nothing to do, only be sure, the situation is set with the good value + LOGGER.trace("Updating situation in already linked shadow."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + return; + } + } + } + // Not linked, need to link + linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result); + // be sure, that the situation is set correctly + LOGGER.trace("Updating situation after shadow was linked."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + } else { + // Link should NOT exist + if (!focusContext.isDelete()) { + PrismObject objectCurrent = focusContext.getObjectCurrent(); + // it is possible that objectCurrent is null (and objectNew is + // non-null), in case of User ADD operation (MID-2176) + if (objectCurrent != null) { + PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF); + if (linkRef != null) { + for (PrismReferenceValue linkRefVal : linkRef.getValues()) { + if (linkRefVal.getOid().equals(projOid)) { + // Linked, need to unlink + unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx, task, result); + } + } + } + } + } + + // This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow. + LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid); + updateSituationInShadow(task, null, context, focusObjectContext, projCtx, result); + // Not linked, that's OK + } + } + + private boolean linkShouldExist(LensFocusContext focusContext, LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { + if (focusContext.isDelete()) { + // if we delete focus, link doesn't exist anymore, but be sure, that the situation is updated in shadow + return false; + } + if (!projCtx.isShadowExistsInRepo()) { + // Nothing to link to + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { + return false; + } + if (isEmptyThombstone(projCtx)) { + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || projCtx.isDelete()) { + return shadowAfterModification != null; + } + if (projCtx.hasPendingOperations()) { + return true; + } + return true; + } + + /** + * Return true if this projection is just a linkRef that points to no + * shadow. + */ + private boolean isEmptyThombstone(LensProjectionContext projCtx) { + return projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().isTombstone() + && projCtx.getObjectCurrent() == null; + } + + private void linkShadow(String userOid, String shadowOid, + LensElementContext focusContext, LensProjectionContext projCtx, Task task, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException { + + Class typeClass = focusContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(typeClass)) { + return; + } + + String channel = focusContext.getLensContext().getChannel(); + + LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid); + OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT); + PrismReferenceValue linkRef = prismContext.itemFactory().createReferenceValue(); + linkRef.setOid(shadowOid); + linkRef.setTargetType(ShadowType.COMPLEX_TYPE); + Collection extends ItemDelta> linkRefDeltas = prismContext.deltaFactory().reference() + .createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef); + + try { + cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result); + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, null); + } catch (ObjectAlreadyExistsException ex) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, ex); + throw new SystemException(ex); + } catch (Throwable t) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, t); + throw t; + } finally { + result.computeStatus(); + ObjectDelta
void recordProjectionExecutionException(Throwable e, - LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) { - subResult.recordFatalError(e); - LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e); - if (decision != null) { - accCtx.setSynchronizationPolicyDecision(decision); - } - } - - private void recordFatalError(OperationResult subResult, OperationResult result, String message, - Throwable e) { - if (message == null) { - message = e.getMessage(); - } - subResult.recordFatalError(e); - if (result != null) { - result.computeStatusComposite(); - } - } - - /** - * Make sure that the account is linked (or unlinked) as needed. - */ - private void updateLinks(LensContext> context, - LensFocusContext focusObjectContext, LensProjectionContext projCtx, - PrismObject shadowAfterModification, - Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { - if (focusObjectContext == null) { - return; - } - Class objectTypeClass = focusObjectContext.getObjectTypeClass(); - if (!FocusType.class.isAssignableFrom(objectTypeClass)) { - return; - } - //noinspection unchecked - LensFocusContext focusContext = (LensFocusContext) focusObjectContext; - - if (projCtx.getResourceShadowDiscriminator() != null - && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { - // Don't mess with links for higher-order contexts. The link should - // be dealt with - // during processing of zero-order context. - return; - } - - String projOid = projCtx.getOid(); - if (projOid == null) { - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - // This seems to be OK. In quite a strange way, but still OK. - return; - } - LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump()); - throw new IllegalStateException("Projection " + projCtx.toHumanReadableString() + " has null OID, this should not happen"); - } - - if (linkShouldExist(projCtx, shadowAfterModification, result)) { - // Link should exist - PrismObject objectCurrent = focusContext.getObjectCurrent(); - if (objectCurrent != null) { - for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) { - if (projOid.equals(linkRef.getOid())) { - // Already linked, nothing to do, only be sure, the situation is set with the good value - LOGGER.trace("Updating situation in already linked shadow."); - updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); - return; - } - } - } - // Not linked, need to link - linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result); - // be sure, that the situation is set correctly - LOGGER.trace("Updating situation after shadow was linked."); - updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); - } else { - // Link should NOT exist - if (!focusContext.isDelete()) { - PrismObject objectCurrent = focusContext.getObjectCurrent(); - // it is possible that objectCurrent is null (and objectNew is - // non-null), in case of User ADD operation (MID-2176) - if (objectCurrent != null) { - PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF); - if (linkRef != null) { - for (PrismReferenceValue linkRefVal : linkRef.getValues()) { - if (linkRefVal.getOid().equals(projOid)) { - // Linked, need to unlink - unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx, task, result); - } - } - } - } - } - - // This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow. - LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid); - updateSituationInShadow(task, null, context, focusObjectContext, projCtx, result); - // Not linked, that's OK - } - } - - private boolean linkShouldExist(LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { - if (!projCtx.isShadowExistsInRepo()) { - // Nothing to link to - return false; - } - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { - return false; - } - if (isEmptyThombstone(projCtx)) { - return false; - } - if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE - || projCtx.isDelete()) { - return shadowAfterModification != null; - } - if (projCtx.hasPendingOperations()) { - return true; - } - return true; - } - - /** - * Return true if this projection is just a linkRef that points to no - * shadow. - */ - private boolean isEmptyThombstone(LensProjectionContext projCtx) { - return projCtx.getResourceShadowDiscriminator() != null - && projCtx.getResourceShadowDiscriminator().isTombstone() - && projCtx.getObjectCurrent() == null; - } - - private void linkShadow(String userOid, String shadowOid, - LensElementContext focusContext, LensProjectionContext projCtx, Task task, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - - Class typeClass = focusContext.getObjectTypeClass(); - if (!FocusType.class.isAssignableFrom(typeClass)) { - return; - } - - String channel = focusContext.getLensContext().getChannel(); - - LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid); - OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT); - PrismReferenceValue linkRef = prismContext.itemFactory().createReferenceValue(); - linkRef.setOid(shadowOid); - linkRef.setTargetType(ShadowType.COMPLEX_TYPE); - Collection extends ItemDelta> linkRefDeltas = prismContext.deltaFactory().reference() - .createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef); - - try { - cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result); - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, - ChangeType.MODIFY, channel, null); - } catch (ObjectAlreadyExistsException ex) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, - ChangeType.MODIFY, channel, ex); - throw new SystemException(ex); - } catch (Throwable t) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, - ChangeType.MODIFY, channel, t); - throw t; - } finally { - result.computeStatus(); - ObjectDelta userDelta = prismContext.deltaFactory().object().createModifyDelta(userOid, linkRefDeltas, typeClass - ); - LensObjectDeltaOperation userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result, - focusContext, projCtx); - focusContext.addToExecutedDeltas(userDeltaOp); - } - - } - - private PrismObjectDefinition getUserDefinition() { - return userDefinition; - } - - private void unlinkShadow(String focusOid, PrismReferenceValue accountRef, - LensElementContext focusContext, LensProjectionContext projCtx, Task task, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - - Class typeClass = focusContext.getObjectTypeClass(); - if (!FocusType.class.isAssignableFrom(typeClass)) { - return; - } - - String channel = focusContext.getLensContext().getChannel(); - - LOGGER.debug("Unlinking shadow {} from focus {}", accountRef.getOid(), focusOid); - OperationResult result = parentResult.createSubresult(OPERATION_UNLINK_ACCOUNT); - Collection extends ItemDelta> accountRefDeltas = prismContext.deltaFactory().reference().createModificationDeleteCollection( - FocusType.F_LINK_REF, getUserDefinition(), accountRef.clone()); - - try { - cacheRepositoryService.modifyObject(typeClass, focusOid, accountRefDeltas, result); - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid, - ChangeType.MODIFY, channel, null); - } catch (ObjectAlreadyExistsException ex) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid, - ChangeType.MODIFY, channel, ex); - result.recordFatalError(ex); - throw new SystemException(ex); - } catch (Throwable t) { - task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid, - ChangeType.MODIFY, channel, t); - throw t; - } finally { - result.computeStatus(); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(focusOid, accountRefDeltas, typeClass - ); - LensObjectDeltaOperation userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result, - focusContext, projCtx); - focusContext.addToExecutedDeltas(userDeltaOp); - } - - } - - private void updateSituationInShadow(Task task, SynchronizationSituationType newSituation, - LensContext> context, LensFocusContext focusContext, LensProjectionContext projectionCtx, - OperationResult parentResult) throws SchemaException { - - String projectionOid = projectionCtx.getOid(); - - OperationResult result = parentResult.createMinorSubresult(OPERATION_UPDATE_SITUATION_IN_SHADOW); - result.addArbitraryObjectAsParam("situation", newSituation); - result.addParam("accountRef", projectionOid); - - projectionCtx.setSynchronizationSituationResolved(newSituation); - - PrismObject currentShadow; - GetOperationOptions getOptions = GetOperationOptions.createNoFetch(); - getOptions.setAllowNotFound(true); - try { - // TODO consider skipping this operation - at least in some cases - currentShadow = provisioning.getObject(ShadowType.class, projectionOid, - SelectorOptions.createCollection(getOptions), task, result); - } catch (ObjectNotFoundException ex) { - LOGGER.trace("Shadow is gone, skipping modifying situation in shadow."); - result.recordSuccess(); - return; - } catch (Exception ex) { - LOGGER.trace("Problem with getting shadow, skipping modifying situation in shadow."); - result.recordPartialError(ex); - return; - } - - SynchronizationSituationType currentSynchronizationSituation = currentShadow.asObjectable().getSynchronizationSituation(); - if (currentSynchronizationSituation == SynchronizationSituationType.DELETED && ShadowUtil.isDead(currentShadow.asObjectable())) { - LOGGER.trace("Skipping update of synchronization situation for deleted dead shadow"); - result.recordSuccess(); - return; - } - - InternalsConfigurationType internalsConfig = context.getInternalsConfiguration(); - boolean cansSkip = - internalsConfig != null && internalsConfig.getSynchronizationSituationUpdating() != null && - Boolean.TRUE.equals(internalsConfig.getSynchronizationSituationUpdating().isSkipWhenNoChange()); - if (cansSkip) { - if (newSituation == currentSynchronizationSituation) { - LOGGER.trace("Skipping update of synchronization situation because there is no change ({})", - currentSynchronizationSituation); - result.recordSuccess(); - return; - } else { - LOGGER.trace("Updating synchronization situation {} -> {}", currentSynchronizationSituation, newSituation); - } - } - - XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); - List> syncSituationDeltas = SynchronizationUtils - .createSynchronizationSituationAndDescriptionDelta(currentShadow, newSituation, task.getChannel(), - projectionCtx.hasFullShadow(), now, prismContext); - - try { - ModelImplUtils.setRequestee(task, focusContext); - ProvisioningOperationOptions options = ProvisioningOperationOptions.createCompletePostponed(false); - options.setDoNotDiscovery(true); - provisioning.modifyObject(ShadowType.class, projectionOid, syncSituationDeltas, null, options, task, result); - LOGGER.trace("Situation in projection {} was updated to {}.", projectionCtx, newSituation); - } catch (ObjectNotFoundException ex) { - // if the object not found exception is thrown, it's ok..probably - // the account was deleted by previous execution of changes..just - // log in the trace the message for the user.. - LOGGER.debug( - "Situation in account could not be updated. Account not found on the resource. Skipping modifying situation in account"); - return; - } catch (Exception ex) { - result.recordFatalError(ex); - throw new SystemException(ex.getMessage(), ex); - } finally { - ModelImplUtils.clearRequestee(task); - } - // if everything is OK, add result of the situation modification to the - // parent result - result.recordSuccess(); - } - - /** - * @return Returns estimate of the object after modification. Or null if the object was deleted. - * NOTE: this is only partially implemented (only for shadow delete). - */ - private PrismObject executeDelta(ObjectDelta objectDelta, - LensElementContext objectContext, LensContext context, ModelExecuteOptions options, - ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult parentResult) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, - PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - if (objectDelta == null) { - throw new IllegalArgumentException("Null change"); - } - - if (objectDelta.getOid() == null) { - objectDelta.setOid(objectContext.getOid()); - } - - objectDelta = computeDeltaToExecute(objectDelta, objectContext); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("computeDeltaToExecute returned:\n{}", - objectDelta != null ? objectDelta.debugDump(1) : "(null)"); - } - - if (objectDelta == null || objectDelta.isEmpty()) { - LOGGER.debug("Skipping execution of delta because it was already executed: {}", objectContext); - return objectContext.getObjectCurrent(); - } - - if (InternalsConfig.consistencyChecks) { - objectDelta.checkConsistence(ConsistencyCheckScope.fromBoolean(consistencyChecks)); - } - - // Other types than focus types may not be definition-complete (e.g. - // accounts and resources are completed in provisioning) - if (FocusType.class.isAssignableFrom(objectDelta.getObjectTypeClass())) { - objectDelta.assertDefinitions(); - } - - LensUtil.setDeltaOldValue(objectContext, objectDelta); - - if (LOGGER.isTraceEnabled()) { - logDeltaExecution(objectDelta, context, resource, null, task); - } - - OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE_DELTA); - PrismObject objectAfterModification = null; - - try { - if (objectDelta.getChangeType() == ChangeType.ADD) { - objectAfterModification = executeAddition(objectDelta, context, objectContext, options, resource, task, result); - } else if (objectDelta.getChangeType() == ChangeType.MODIFY) { - executeModification(objectDelta, context, objectContext, options, conflictResolution, resource, task, result); - } else if (objectDelta.getChangeType() == ChangeType.DELETE) { - objectAfterModification = executeDeletion(objectDelta, context, objectContext, options, resource, task, result); - } - - // To make sure that the OID is set (e.g. after ADD operation) - LensUtil.setContextOid(context, objectContext, objectDelta.getOid()); - - } finally { - - result.computeStatus(); - if (objectContext != null) { - if (!objectDelta.hasCompleteDefinition()) { - throw new SchemaException("object delta does not have complete definition"); - } - LensObjectDeltaOperation objectDeltaOp = LensUtil.createObjectDeltaOperation( - objectDelta.clone(), result, objectContext, null, resource); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Recording executed delta:\n{}", objectDeltaOp.shorterDebugDump(1)); - } - objectContext.addToExecutedDeltas(objectDeltaOp); - if (result.isTracingNormal(ModelExecuteDeltaTraceType.class)) { - TraceType trace = new ModelExecuteDeltaTraceType(prismContext) - .delta(objectDeltaOp.clone().toLensObjectDeltaOperationType()); // todo kill operation result? - result.addTrace(trace); - } - } else { - if (result.isTracingNormal(ModelExecuteDeltaTraceType.class)) { - LensObjectDeltaOperation objectDeltaOp = new LensObjectDeltaOperation<>(objectDelta); // todo - TraceType trace = new ModelExecuteDeltaTraceType(prismContext) - .delta(objectDeltaOp.toLensObjectDeltaOperationType()); - result.addTrace(trace); - } - } - - if (LOGGER.isDebugEnabled()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("EXECUTION result {}", result.getLastSubresult()); - } else { - // Execution of deltas was not logged yet - logDeltaExecution(objectDelta, context, resource, result.getLastSubresult(), task); - } - } - } - - return objectAfterModification; - } - - private void removeExecutedItemDeltas( - ObjectDelta objectDelta, LensElementContext objectContext) { - if (objectContext == null) { - return; - } - - if (objectDelta == null || objectDelta.isEmpty()) { - return; - } - - if (objectDelta.getModifications() == null || objectDelta.getModifications().isEmpty()) { - return; - } - - List> executedDeltas = objectContext.getExecutedDeltas(); - for (LensObjectDeltaOperation executedDelta : executedDeltas) { - ObjectDelta executed = executedDelta.getObjectDelta(); - Iterator extends ItemDelta> objectDeltaIterator = objectDelta.getModifications().iterator(); - while (objectDeltaIterator.hasNext()) { - ItemDelta d = objectDeltaIterator.next(); - if (executed.containsModification(d, EquivalenceStrategy.LITERAL_IGNORE_METADATA) || d.isEmpty()) { // todo why literal? - objectDeltaIterator.remove(); - } - } - } - } - -// // TODO beware - what if the delta was executed but not successfully? -// private boolean alreadyExecuted(ObjectDelta objectDelta, -// LensElementContext objectContext) { -// if (objectContext == null) { -// return false; -// } -// if (LOGGER.isTraceEnabled()) { -// LOGGER.trace("Checking for already executed delta:\n{}\nIn deltas:\n{}", objectDelta.debugDump(), -// DebugUtil.debugDump(objectContext.getExecutedDeltas())); -// } -// return ObjectDeltaOperation.containsDelta(objectContext.getExecutedDeltas(), objectDelta); -// } - - /** - * Was this object already added? (temporary method, should be removed soon) - */ - private boolean wasAdded(List> executedOperations, - String oid) { - for (LensObjectDeltaOperation operation : executedOperations) { - if (operation.getObjectDelta().isAdd() && oid.equals(operation.getObjectDelta().getOid()) - && !operation.getExecutionResult().isFatalError()) { - return true; - } - } - return false; - } - - /** - * Computes delta to execute, given a list of already executes deltas. See - * below. - */ - private ObjectDelta computeDeltaToExecute(ObjectDelta objectDelta, - LensElementContext objectContext) throws SchemaException { - if (objectContext == null) { - return objectDelta; - } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Computing delta to execute from delta:\n{}\nGiven these executed deltas:\n{}", - objectDelta.debugDump(1), LensObjectDeltaOperation.shorterDebugDump(objectContext.getExecutedDeltas(), 1)); - } - List> executedDeltas = objectContext.getExecutedDeltas(); - ObjectDelta diffDelta = computeDiffDelta(executedDeltas, objectDelta); - - // One more check: is the computed delta idempotent related to objectCurrent? - // Currently we deal only with focusContext because of safety; and also because this check is a reaction - // in change to focus context secondary delta swallowing code (MID-5207). - // - // LookupTableType operation optimization is not available here, because it looks like that isRedundant - // does not work reliably for key-based row deletions (MID-5276). - if (diffDelta != null && objectContext instanceof LensFocusContext> && - !objectContext.isOfType(LookupTableType.class) && - diffDelta.isRedundant(objectContext.getObjectCurrent(), false)) { - LOGGER.trace("delta is idempotent related to {}", objectContext.getObjectCurrent()); - return null; - } - return diffDelta; - } - - /** - * Compute a "difference delta" - given that executedDeltas were executed, - * and objectDelta is about to be executed; eliminates parts that have - * already been done. It is meant as a kind of optimization (for MODIFY - * deltas) and error avoidance (for ADD deltas). - * - * Explanation for ADD deltas: there are situations where an execution wave - * is restarted - when unexpected AlreadyExistsException is reported from - * provisioning. However, in such cases, duplicate ADD Focus deltas were - * generated. So we (TEMPORARILY!) decided to filter them out here. - * - * Unfortunately, this mechanism is not well-defined, and seems to work more - * "by accident" than "by design". It should be replaced with something more - * serious. Perhaps by re-reading current focus state when repeating a wave? - * Actually, it is a supplement for rewriting ADD->MODIFY deltas in - * LensElementContext.getFixedPrimaryDelta. That method converts primary - * deltas (and as far as I know, that is the only place where this problem - * should occur). Nevertheless, for historical and safety reasons I keep - * also the processing in this method. - * - * Anyway, currently it treats only three cases: - * 1) if the objectDelta is present in the list of executed deltas - * 2) if the objectDelta is ADD, and another ADD delta is there (then the difference is computed) - * 3) if objectDelta is MODIFY or DELETE and previous delta was MODIFY - */ - private ObjectDelta computeDiffDelta( - List extends ObjectDeltaOperation> executedDeltas, ObjectDelta objectDelta) { - if (executedDeltas == null || executedDeltas.isEmpty()) { - return objectDelta; - } - - // any delta related to our OID, not ending with fatal error - ObjectDeltaOperation lastRelated = findLastRelatedDelta(executedDeltas, objectDelta); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("findLastRelatedDelta returned:\n{}", - lastRelated != null ? lastRelated.shorterDebugDump(1) : " (null)"); - } - if (lastRelated == null) { - return objectDelta; // nothing found, let us apply our delta - } - if (lastRelated.getExecutionResult().isSuccess() && lastRelated.containsDelta(objectDelta)) { - return null; // case 1 - exact match found with SUCCESS result, - // let's skip the processing of our delta - } - if (!objectDelta.isAdd()) { - if (lastRelated.getObjectDelta().isDelete()) { - return null; // case 3 - } else { - return objectDelta; // MODIFY or DELETE delta after ADD or MODIFY delta - we may safely apply it - } - } - // determine if we got case 2 - if (lastRelated.getObjectDelta().isDelete()) { - return objectDelta; // we can (and should) apply the ADD delta as a - // whole, because the object was deleted - } - // let us treat the most simple case here - meaning we have existing ADD - // delta and nothing more - // TODO add more sophistication if needed - if (!lastRelated.getObjectDelta().isAdd()) { - return objectDelta; // this will probably fail, but ... - } - // at this point we know that ADD was more-or-less successfully - // executed, so let's compute the difference, creating a MODIFY delta - PrismObject alreadyAdded = lastRelated.getObjectDelta().getObjectToAdd(); - PrismObject toBeAddedNow = objectDelta.getObjectToAdd(); - return alreadyAdded.diff(toBeAddedNow); - } - - private ObjectDeltaOperation findLastRelatedDelta( - List extends ObjectDeltaOperation> executedDeltas, ObjectDelta objectDelta) { - for (int i = executedDeltas.size() - 1; i >= 0; i--) { - ObjectDeltaOperation currentOdo = executedDeltas.get(i); - if (currentOdo.getExecutionResult().isFatalError()) { - continue; - } - ObjectDelta current = currentOdo.getObjectDelta(); - - if (current.equals(objectDelta)) { - return currentOdo; - } - - String oid1 = current.isAdd() ? current.getObjectToAdd().getOid() : current.getOid(); - String oid2 = objectDelta.isAdd() ? objectDelta.getObjectToAdd().getOid() - : objectDelta.getOid(); - if (oid1 != null && oid2 != null) { - if (oid1.equals(oid2)) { - return currentOdo; - } - continue; - } - // ADD-MODIFY and ADD-DELETE combinations lead to applying whole - // delta (as a result of computeDiffDelta) - // so we can be lazy and check only ADD-ADD combinations here... - if (!current.isAdd() || !objectDelta.isAdd()) { - continue; - } - // we simply check the type (for focus objects) and - // resource+kind+intent (for shadows) - PrismObject currentObject = current.getObjectToAdd(); - PrismObject objectTypeToAdd = objectDelta.getObjectToAdd(); - Class currentObjectClass = currentObject.getCompileTimeClass(); - Class objectTypeToAddClass = objectTypeToAdd.getCompileTimeClass(); - if (currentObjectClass == null || !currentObjectClass.equals(objectTypeToAddClass)) { - continue; - } - if (FocusType.class.isAssignableFrom(currentObjectClass)) { - return currentOdo; // we suppose there is only one delta of - // Focus class - } - } - return null; - } - - private ProvisioningOperationOptions copyFromModelOptions(ModelExecuteOptions options) { - ProvisioningOperationOptions provisioningOptions = new ProvisioningOperationOptions(); - if (options == null) { - return provisioningOptions; - } - - provisioningOptions.setForce(options.getForce()); - provisioningOptions.setOverwrite(options.getOverwrite()); - return provisioningOptions; - } - - private ProvisioningOperationOptions getProvisioningOptions(LensContext context, - ModelExecuteOptions modelOptions, PrismObject existingShadow, ObjectDelta delta) throws SecurityViolationException { - if (modelOptions == null && context != null) { - modelOptions = context.getOptions(); - } - ProvisioningOperationOptions provisioningOptions = copyFromModelOptions(modelOptions); - - if (executeAsSelf(context, modelOptions, existingShadow, delta)) { - LOGGER.trace("Setting 'execute as self' provisioning option for {}", existingShadow); - provisioningOptions.setRunAsAccountOid(existingShadow.getOid()); - } - - if (context != null && context.getChannel() != null) { - - if (context.getChannel().equals(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_RECON))) { - // TODO: this is probably wrong. We should not have special case - // for recon channel! This should be handled by the provisioning task - // setting the right options there. - provisioningOptions.setCompletePostponed(false); - } - - if (context.getChannel().equals(SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI)) { - // We want to avoid endless loops in error handling. - provisioningOptions.setDoNotDiscovery(true); - } - } - - return provisioningOptions; - } - - // This is a bit of black magic. We only want to execute as self if there a user is changing its own password - // and we also have old password value. - // Later, this should be improved. Maybe we need special model operation option for this? Or maybe it should be somehow - // automatically detected based on resource capabilities? We do not know yet. Therefore let's do the simplest possible - // thing. Otherwise we might do something that we will later regret. - private boolean executeAsSelf(LensContext context, - ModelExecuteOptions modelOptions, PrismObject existingShadow, ObjectDelta delta) throws SecurityViolationException { - if (existingShadow == null) { - return false; - } - - if (!SchemaConstants.CHANNEL_GUI_SELF_SERVICE_URI.equals(context.getChannel())) { - return false; - } - - if (delta == null) { - return false; - } - if (!delta.isModify()) { - return false; - } - PropertyDelta passwordDelta = delta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - if (passwordDelta == null) { - return false; - } - if (passwordDelta.getEstimatedOldValues() == null || passwordDelta.getEstimatedOldValues().isEmpty()) { - return false; - } - ProtectedStringType oldPassword = passwordDelta.getEstimatedOldValues().iterator().next().getValue(); - if (!oldPassword.canGetCleartext()) { - return false; - } - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return false; - } - if (!focusContext.represents(UserType.class)) { - return false; - } - - MidPointPrincipal principal = securityContextManager.getPrincipal(); - if (principal == null) { - return false; - } - FocusType loggedInUser = principal.getFocus(); - - if (!loggedInUser.getOid().equals(focusContext.getOid())) { - return false; - } - return true; - } - - private void logDeltaExecution(ObjectDelta objectDelta, - LensContext context, ResourceType resource, OperationResult result, Task task) { - StringBuilder sb = new StringBuilder(); - sb.append("---[ "); - if (result == null) { - sb.append("Going to EXECUTE"); - } else { - sb.append("EXECUTED"); - } - sb.append(" delta of ").append(objectDelta.getObjectTypeClass().getSimpleName()); - sb.append(" ]---------------------\n"); - DebugUtil.debugDumpLabel(sb, "Channel", 0); - sb.append(" ").append(LensUtil.getChannel(context, task)).append("\n"); - if (context != null) { - DebugUtil.debugDumpLabel(sb, "Wave", 0); - sb.append(" ").append(context.getExecutionWave()).append("\n"); - } - if (resource != null) { - sb.append("Resource: ").append(resource.toString()).append("\n"); - } - sb.append(objectDelta.debugDump()); - sb.append("\n"); - if (result != null) { - DebugUtil.debugDumpLabel(sb, "Result", 0); - sb.append(" ").append(result.getStatus()).append(": ").append(result.getMessage()); - } - sb.append("\n--------------------------------------------------"); - - LOGGER.debug("\n{}", sb); - } - - private OwnerResolver createOwnerResolver(final LensContext context, Task task, - OperationResult result) { - return new LensOwnerResolver<>(context, objectResolver, task, result); - } - - private PrismObject executeAddition(ObjectDelta change, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - PrismObject objectToAdd = change.getObjectToAdd(); - - for (ItemDelta delta : change.getModifications()) { - delta.applyTo(objectToAdd); - } - change.getModifications().clear(); - - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - T objectTypeToAdd = objectToAdd.asObjectable(); - try { - securityEnforcer.authorize(ModelAuthorizationAction.ADD.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectAdd(objectToAdd), ownerResolver, task, result); - - metadataManager.applyMetadataAdd(context, objectToAdd, clock.currentTimeXMLGregorianCalendar(), task, result); - - if (options == null) { - options = context.getOptions(); - } - - RepoAddOptions addOpt = new RepoAddOptions(); - if (ModelExecuteOptions.isOverwrite(options)) { - addOpt.setOverwrite(true); - } - if (ModelExecuteOptions.isNoCrypt(options)) { - addOpt.setAllowUnencryptedValues(true); - } - - String oid; - if (objectTypeToAdd instanceof TaskType) { - oid = addTask((TaskType) objectTypeToAdd, addOpt, result); - } else if (objectTypeToAdd instanceof NodeType) { - throw new UnsupportedOperationException("NodeType cannot be added using model interface"); - } else if (ObjectTypes.isManagedByProvisioning(objectTypeToAdd)) { - - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) change); - - oid = addProvisioningObject(objectToAdd, context, objectContext, provisioningOptions, - resource, task, result); - if (oid == null) { - throw new SystemException( - "Provisioning addObject returned null OID while adding " + objectToAdd); - } - result.addReturn("createdAccountOid", oid); - } else { - FocusConstraintsChecker.clearCacheFor(objectToAdd.asObjectable().getName()); - - oid = cacheRepositoryService.addObject(objectToAdd, addOpt, result); - if (oid == null) { - throw new SystemException("Repository addObject returned null OID while adding " + objectToAdd); - } - } - if (!change.isImmutable()) { - change.setOid(oid); - } - objectToAdd.setOid(oid); - task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), oid, - ChangeType.ADD, context.getChannel(), null); - return objectToAdd; - } catch (Throwable t) { - task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), null, - ChangeType.ADD, context.getChannel(), t); - if (objectTypeToAdd instanceof ShadowType) { - handleProvisioningError(resource, t, task, result); - ((LensProjectionContext) objectContext).setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - return null; - } - throw t; - - } - } - - private void handleProvisioningError(ResourceType resource, Throwable t, Task task, OperationResult result) throws ObjectNotFoundException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException, CommunicationException, SchemaException { - ErrorSelectorType errorSelectorType = ResourceTypeUtil.getConnectorErrorCriticality(resource); - CriticalityType criticality = ExceptionUtil.getCriticality(errorSelectorType, t, CriticalityType.FATAL); - RepoCommonUtils.processErrorCriticality(task, criticality, t, result); - if (CriticalityType.IGNORE == criticality) { - result.muteLastSubresultError(); - } - } - - private PrismObject executeDeletion(ObjectDelta change, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - ObjectAlreadyExistsException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - String oid = change.getOid(); - Class objectTypeClass = change.getObjectTypeClass(); - - PrismObject objectOld = objectContext.getObjectOld(); - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - PrismObject objectAfterModification = null; - try { - securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelete(objectOld), ownerResolver, task, result); - - if (TaskType.class.isAssignableFrom(objectTypeClass)) { - taskManager.deleteTask(oid, result); - } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { - taskManager.deleteNode(oid, result); - } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) change); - try { - objectAfterModification = deleteProvisioningObject(objectTypeClass, oid, context, objectContext, - provisioningOptions, resource, task, result); - } catch (ObjectNotFoundException e) { - // Object that we wanted to delete is already gone. This can - // happen in some race conditions. - // As the resulting state is the same as we wanted it to be - // we will not complain and we will go on. - LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid, - objectTypeClass); - result.muteLastSubresultError(); - } - } else { - try { - cacheRepositoryService.deleteObject(objectTypeClass, oid, result); - } catch (ObjectNotFoundException e) { - // Object that we wanted to delete is already gone. This can - // happen in some race conditions. - // As the resulting state is the same as we wanted it to be - // we will not complain and we will go on. - LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid, - objectTypeClass); - result.muteLastSubresultError(); - } - } - task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE, - context.getChannel(), null); - } catch (Throwable t) { - task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE, - context.getChannel(), t); - - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - handleProvisioningError(resource, t, task, result); - return objectContext.getObjectCurrent(); - } - - throw t; - } - - return objectAfterModification; - } - - private void executeModification(ObjectDelta delta, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, CommunicationException, - ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - Class objectTypeClass = delta.getObjectTypeClass(); - - // We need current object here. The current object is used to get data for id-only container delete deltas, - // replace deltas and so on. The authorization code can figure out new object if needed, but it needs - // current object to start from. - // We cannot use old object here. That would fail in multi-wave executions. We want object that has all the previous - // wave changes already applied. - PrismObject baseObject = objectContext.getObjectCurrent(); - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - try { - securityEnforcer.authorize(ModelAuthorizationAction.MODIFY.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelta(baseObject, delta), ownerResolver, task, result); - - if (shouldApplyModifyMetadata(objectTypeClass, context.getSystemConfigurationType())) { - metadataManager.applyMetadataModify(delta, objectContext, objectTypeClass, - clock.currentTimeXMLGregorianCalendar(), task, context, result); - } - - if (delta.isEmpty()) { - // Nothing to do - return; - } - - if (TaskType.class.isAssignableFrom(objectTypeClass)) { - taskManager.modifyTask(delta.getOid(), delta.getModifications(), result); - } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { - throw new UnsupportedOperationException("NodeType is not modifiable using model interface"); - } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) delta); - String oid = modifyProvisioningObject(objectTypeClass, delta.getOid(), - delta.getModifications(), context, objectContext, provisioningOptions, resource, - task, result); - if (!oid.equals(delta.getOid())) { - delta.setOid(oid); - } - } else { - FocusConstraintsChecker.clearCacheForDelta(delta.getModifications()); - ModificationPrecondition precondition = null; - if (conflictResolution != null) { - String readVersion = objectContext.getObjectReadVersion(); - if (readVersion != null) { - LOGGER.trace("Modification with precondition, readVersion={}", readVersion); - precondition = new VersionPrecondition<>(readVersion); - } else { - LOGGER.warn("Requested careful modification of {}, but there is no read version", objectContext.getHumanReadableName()); - } - } - cacheRepositoryService.modifyObject(objectTypeClass, delta.getOid(), - delta.getModifications(), precondition, null, result); - } - task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY, - context.getChannel(), null); - } catch (Throwable t) { - task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY, - context.getChannel(), t); - throw t; - } - } - - private boolean shouldApplyModifyMetadata(Class objectTypeClass, SystemConfigurationType config) { - if (!ShadowType.class.equals(objectTypeClass)) { - return true; - } else if (config == null || config.getInternals() == null || config.getInternals().getShadowMetadataRecording() == null) { - return true; - } else { - MetadataRecordingStrategyType recording = config.getInternals().getShadowMetadataRecording(); - return !Boolean.TRUE.equals(recording.isSkipOnModify()); - } - } - - private String addTask(TaskType task, RepoAddOptions addOpt, OperationResult result) - throws ObjectAlreadyExistsException { - try { - return taskManager.addTask(task.asPrismObject(), addOpt, result); - } catch (ObjectAlreadyExistsException ex) { - throw ex; - } catch (Exception ex) { - LoggingUtils.logException(LOGGER, "Couldn't add object {} to task manager", ex, task.getName()); - throw new SystemException(ex.getMessage(), ex); - } - } - - private String addProvisioningObject(PrismObject object, - LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - ObjectAlreadyExistsException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException { - - if (object.canRepresent(ShadowType.class)) { - ShadowType shadow = (ShadowType) object.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadow); - if (resourceOid == null) { - throw new IllegalArgumentException("Resource OID is null in shadow"); - } - } - - OperationProvisioningScriptsType scripts = null; - if (object.canRepresent(ShadowType.class)) { - scripts = prepareScripts(object, context, objectContext, ProvisioningOperationTypeType.ADD, - resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - String oid = provisioning.addObject(object, scripts, options, task, result); - ModelImplUtils.clearRequestee(task); - return oid; - } - - private PrismObject deleteProvisioningObject( - Class objectTypeClass, String oid, LensContext context, LensElementContext objectContext, - ProvisioningOperationOptions options, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, PolicyViolationException { - - PrismObject shadowToModify = null; - OperationProvisioningScriptsType scripts = null; - try { - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - shadowToModify = provisioning.getObject(objectTypeClass, oid, - SelectorOptions.createCollection(rootOpts), task, result); - } catch (ObjectNotFoundException ex) { - // this is almost OK, mute the error and try to delete account (it - // will fail if something is wrong) - result.muteLastSubresultError(); - } - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - scripts = prepareScripts(shadowToModify, context, objectContext, - ProvisioningOperationTypeType.DELETE, resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - PrismObject objectAfterModification = provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result); - ModelImplUtils.clearRequestee(task); - return objectAfterModification; - } - - private String modifyProvisioningObject( - Class objectTypeClass, String oid, Collection extends ItemDelta> modifications, - LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - - PrismObject shadowToModify = null; - OperationProvisioningScriptsType scripts = null; - try { - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - shadowToModify = provisioning.getObject(objectTypeClass, oid, - SelectorOptions.createCollection(rootOpts), task, result); - } catch (ObjectNotFoundException e) { - // We do not want the operation to fail here. The object might have - // been re-created on the resource - // or discovery might re-create it. So simply ignore this error and - // give provisioning a chance to fail - // properly. - result.muteLastSubresultError(); - LOGGER.warn("Repository object {}: {} is gone. But trying to modify resource object anyway", - objectTypeClass, oid); - } - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - scripts = prepareScripts(shadowToModify, context, objectContext, - ProvisioningOperationTypeType.MODIFY, resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - String changedOid = provisioning.modifyObject(objectTypeClass, oid, modifications, scripts, options, - task, result); - ModelImplUtils.clearRequestee(task); - return changedOid; - } - - private OperationProvisioningScriptsType prepareScripts( - PrismObject changedObject, LensContext context, LensElementContext objectContext, - ProvisioningOperationTypeType operation, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing scripts."); - return null; - } - OperationProvisioningScriptsType resourceScripts = resource.getScripts(); - PrismObject resourceObject = (PrismObject) changedObject; - - PrismObject user = null; - if (context.getFocusContext() != null) { - if (context.getFocusContext().getObjectNew() != null) { - user = context.getFocusContext().getObjectNew(); - } else if (context.getFocusContext().getObjectCurrent() != null) { - user = context.getFocusContext().getObjectCurrent(); - } else if (context.getFocusContext().getObjectOld() != null) { - user = context.getFocusContext().getObjectOld(); - } - } - - LensProjectionContext projectionCtx = (LensProjectionContext) objectContext; - PrismObject shadow = null; - if (projectionCtx.getObjectNew() != null) { - shadow = projectionCtx.getObjectNew(); - } else if (projectionCtx.getObjectCurrent() != null) { - shadow = projectionCtx.getObjectCurrent(); - } else { - shadow = projectionCtx.getObjectOld(); - } - - if (shadow == null) { - //put at least something - shadow = resourceObject.clone(); - } - - ResourceShadowDiscriminator discr = ((LensProjectionContext) objectContext) - .getResourceShadowDiscriminator(); - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, discr, - resource.asPrismObject(), context.getSystemConfiguration(), objectContext, prismContext); - // Having delta in provisioning scripts may be very useful. E.g. the script can optimize execution of expensive operations. - variables.put(ExpressionConstants.VAR_DELTA, projectionCtx.getDelta(), ObjectDelta.class); - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, (LensProjectionContext) objectContext, task, result)); - try { - return evaluateScript(resourceScripts, discr, operation, null, variables, expressionProfile, context, objectContext, task, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - } - - private OperationProvisioningScriptsType evaluateScript(OperationProvisioningScriptsType resourceScripts, - ResourceShadowDiscriminator discr, ProvisioningOperationTypeType operation, BeforeAfterType order, - ExpressionVariables variables, ExpressionProfile expressionProfile, LensContext> context, - LensElementContext> objectContext, Task task, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - OperationProvisioningScriptsType outScripts = new OperationProvisioningScriptsType(); - - if (resourceScripts != null) { - OperationProvisioningScriptsType scripts = resourceScripts.clone(); - for (OperationProvisioningScriptType script : scripts.getScript()) { - if (discr != null) { - if (script.getKind() != null && !script.getKind().isEmpty() - && !script.getKind().contains(discr.getKind())) { - continue; - } - if (script.getIntent() != null && !script.getIntent().isEmpty() - && !script.getIntent().contains(discr.getIntent()) && discr.getIntent() != null) { - continue; - } - } - if (operation != null) { - if (!script.getOperation().contains(operation)) { - continue; - } - } - if (order != null) { - if (order != null && order != script.getOrder()) { - continue; - } - } - // Let's do the most expensive evaluation last - if (!evaluateScriptCondition(script, variables, expressionProfile, task, result)) { - continue; - } - for (ProvisioningScriptArgumentType argument : script.getArgument()) { - evaluateScriptArgument(argument, variables, context, objectContext, task, result); - } - outScripts.getScript().add(script); - } - } - - return outScripts; - } - - private boolean evaluateScriptCondition(OperationProvisioningScriptType script, - ExpressionVariables variables, ExpressionProfile expressionProfile, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType condition = script.getCondition(); - if (condition == null) { - return true; - } - - PrismPropertyValue conditionOutput = ExpressionUtil.evaluateCondition(variables, condition, expressionProfile, expressionFactory, " condition for provisioning script ", task, result); - if (conditionOutput == null) { - return true; - } - - Boolean conditionOutputValue = conditionOutput.getValue(); - - return BooleanUtils.isNotFalse(conditionOutputValue); - - } - - private void evaluateScriptArgument(ProvisioningScriptArgumentType argument, - ExpressionVariables variables, LensContext> context, - LensElementContext> objectContext, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - - final QName fakeScriptArgumentName = new QName(SchemaConstants.NS_C, "arg"); - - PrismPropertyDefinition scriptArgumentDefinition = prismContext.definitionFactory().createPropertyDefinition( - fakeScriptArgumentName, DOMUtil.XSD_STRING); - - String shortDesc = "Provisioning script argument expression"; - Expression, PrismPropertyDefinition> expression = expressionFactory - .makeExpression(argument, scriptArgumentDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); - ExpressionEnvironment, ?, ?> env = new ExpressionEnvironment<>(context, - objectContext instanceof LensProjectionContext ? (LensProjectionContext) objectContext : null, task, result); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, env, result); - - Collection> nonNegativeValues = null; - if (outputTriple != null) { - nonNegativeValues = outputTriple.getNonNegativeValues(); - } - - // replace dynamic script with static value.. - XNodeFactory factory = prismContext.xnodeFactory(); - - argument.getExpressionEvaluator().clear(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - // We need to create at least one evaluator. Otherwise the - // expression code will complain - JAXBElement el = new JAXBElement<>(SchemaConstants.C_VALUE, RawType.class, new RawType(prismContext)); - argument.getExpressionEvaluator().add(el); - - } else { - for (PrismPropertyValue val : nonNegativeValues) { - PrimitiveXNode prim = factory.primitive(val.getValue(), DOMUtil.XSD_STRING); - JAXBElement el = new JAXBElement<>(SchemaConstants.C_VALUE, RawType.class, new RawType(prim, prismContext)); - argument.getExpressionEvaluator().add(el); - } - } - } - - private void executeReconciliationScript( - LensProjectionContext projContext, LensContext context, BeforeAfterType order, Task task, - OperationResult parentResult) throws SchemaException, ObjectNotFoundException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectAlreadyExistsException { - - if (!projContext.isDoReconciliation()) { - return; - } - - ResourceType resource = projContext.getResource(); - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts."); - return; - } - - OperationProvisioningScriptsType resourceScripts = resource.getScripts(); - if (resourceScripts == null) { - return; - } - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - executeProvisioningScripts(context, projContext, resourceScripts, ProvisioningOperationTypeType.RECONCILE, order, expressionProfile, task, parentResult); - } - - private Object executeProvisioningScripts(LensContext context, LensProjectionContext projContext, - OperationProvisioningScriptsType scripts, ProvisioningOperationTypeType operation, BeforeAfterType order, ExpressionProfile expressionProfile, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException, ObjectAlreadyExistsException { - - ResourceType resource = projContext.getResource(); - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts."); - return null; - } - - PrismObject user = null; - PrismObject shadow = null; - - if (context.getFocusContext() != null) { - if (context.getFocusContext().getObjectNew() != null) { - user = context.getFocusContext().getObjectNew(); - } else if (context.getFocusContext().getObjectOld() != null) { - user = context.getFocusContext().getObjectOld(); - } - // if (order == ProvisioningScriptOrderType.BEFORE) { - // user = context.getFocusContext().getObjectOld(); - // } else if (order == ProvisioningScriptOrderType.AFTER) { - // user = context.getFocusContext().getObjectNew(); - // } else { - // throw new IllegalArgumentException("Unknown order "+order); - // } - } - - if (order == BeforeAfterType.BEFORE) { - shadow = (PrismObject) projContext.getObjectOld(); - } else if (order == BeforeAfterType.AFTER) { - shadow = (PrismObject) projContext.getObjectNew(); - } else { - shadow = (PrismObject) projContext.getObjectCurrent(); - } - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, - projContext.getResourceShadowDiscriminator(), resource.asPrismObject(), - context.getSystemConfiguration(), projContext, prismContext); - Object scriptResult = null; - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, projContext, task, parentResult)); - try { - OperationProvisioningScriptsType evaluatedScript = evaluateScript(scripts, - projContext.getResourceShadowDiscriminator(), operation, order, - variables, expressionProfile, context, projContext, task, parentResult); - for (OperationProvisioningScriptType script : evaluatedScript.getScript()) { - ModelImplUtils.setRequestee(task, context); - scriptResult = provisioning.executeScript(resource.getOid(), script, task, parentResult); - ModelImplUtils.clearRequestee(task); - } - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - return scriptResult; - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.FOCUS_OPERATION; +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.RESOURCE_OBJECT_OPERATION; +import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; +import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerables; +import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.xml.bind.JAXBElement; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import org.apache.commons.lang.BooleanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.common.SynchronizationUtils; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.ProgressInformation; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; +import com.evolveum.midpoint.prism.xnode.XNodeFactory; +import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.api.*; +import com.evolveum.midpoint.repo.common.expression.*; +import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.OwnerResolver; +import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import com.evolveum.prism.xml.ns._public.types_3.RawType; + +/** + * @author semancik + */ +@Component +public class ChangeExecutor { + + private static final Trace LOGGER = TraceManager.getTrace(ChangeExecutor.class); + + private static final String OPERATION_EXECUTE_DELTA = ChangeExecutor.class.getName() + ".executeDelta"; + private static final String OPERATION_EXECUTE = ChangeExecutor.class.getName() + ".execute"; + private static final String OPERATION_EXECUTE_FOCUS = OPERATION_EXECUTE + ".focus"; + private static final String OPERATION_EXECUTE_PROJECTION = OPERATION_EXECUTE + ".projection"; + private static final String OPERATION_LINK_ACCOUNT = ChangeExecutor.class.getName() + ".linkShadow"; + private static final String OPERATION_UNLINK_ACCOUNT = ChangeExecutor.class.getName() + ".unlinkShadow"; + private static final String OPERATION_UPDATE_SITUATION_IN_SHADOW = ChangeExecutor.class.getName() + ".updateSituationInShadow"; + + @Autowired private transient TaskManager taskManager; + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; + @Autowired private ProvisioningService provisioning; + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private SecurityEnforcer securityEnforcer; + @Autowired private SecurityContextManager securityContextManager; + @Autowired private Clock clock; + @Autowired private ModelObjectResolver objectResolver; + @Autowired private OperationalDataManager metadataManager; + @Autowired private CredentialsProcessor credentialsProcessor; + + private PrismObjectDefinition userDefinition = null; + private PrismObjectDefinition shadowDefinition = null; + + @PostConstruct + private void locateDefinitions() { + userDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(UserType.class); + shadowDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(ShadowType.class); + } + + // returns true if current operation has to be restarted, see + // ObjectAlreadyExistsException handling (TODO specify more exactly) + public boolean executeChanges(LensContext context, Task task, + OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException, PreconditionViolationException, PolicyViolationException { + + OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE); + + try { + + // FOCUS + + context.checkAbortRequested(); + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + ObjectDelta focusDelta = focusContext.getWaveExecutableDelta(context.getExecutionWave()); + + focusDelta = applyPendingObjectPolicyStateModifications(focusContext, focusDelta); + focusDelta = applyPendingAssignmentPolicyStateModifications(focusContext, focusDelta); + + if (focusDelta == null && !context.hasProjectionChange()) { + LOGGER.trace("Skipping focus change execute, because user delta is null"); + } else { + + if (focusDelta == null) { + focusDelta = focusContext.getObjectAny().createModifyDelta(); + } + + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + applyObjectPolicy(focusContext, focusDelta, archetypePolicy); + + OperationResult subResult = result.createSubresult( + OPERATION_EXECUTE_FOCUS + "." + focusContext.getObjectTypeClass().getSimpleName()); + + try { + // Will remove credential deltas or hash them + focusDelta = credentialsProcessor.transformFocusExecutionDelta(context, focusDelta); + } catch (EncryptionException e) { + recordFatalError(subResult, result, null, e); + result.computeStatus(); + throw new SystemException(e.getMessage(), e); + } + + applyLastProvisioningTimestamp(context, focusDelta); + + try { + + context.reportProgress(new ProgressInformation(FOCUS_OPERATION, ENTERING)); + + ConflictResolutionType conflictResolution = ModelExecuteOptions + .getFocusConflictResolution(context.getOptions()); + + executeDelta(focusDelta, focusContext, context, null, conflictResolution, null, task, subResult); + + if (focusDelta.isAdd() && focusDelta.getOid() != null) { + // The watcher can already exist; if the OID was pre-existing in the object. + if (context.getFocusConflictWatcher() == null) { + ConflictWatcher watcher = context + .createAndRegisterFocusConflictWatcher(focusDelta.getOid(), cacheRepositoryService); + watcher.setExpectedVersion(focusDelta.getObjectToAdd().getVersion()); + } + } + subResult.computeStatus(); + + } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException | RuntimeException e) { + recordFatalError(subResult, result, null, e); + throw e; + + } catch (PreconditionViolationException e) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Modification precondition failed for {}: {}", focusContext.getHumanReadableName(), + e.getMessage()); + } + // TODO: fatal error if the conflict resolution is "error" (later) + result.recordHandledError(e); + throw e; + + } catch (ObjectAlreadyExistsException e) { + subResult.computeStatus(); + if (!subResult.isSuccess() && !subResult.isHandledError()) { + subResult.recordFatalError(e); + } + result.computeStatusComposite(); + throw e; + } finally { + context.reportProgress(new ProgressInformation(FOCUS_OPERATION, subResult)); + } + } + } + + // PROJECTIONS + + context.checkAbortRequested(); + + boolean restartRequested = false; + + for (LensProjectionContext projCtx : context.getProjectionContexts()) { + if (projCtx.getWave() != context.getExecutionWave()) { + LOGGER.trace("Skipping projection context {} because its wave ({}) is different from execution wave ({})", + projCtx.toHumanReadableString(), projCtx.getWave(), context.getExecutionWave()); + continue; + } + + if (!projCtx.isCanProject()) { + LOGGER.trace("Skipping projection context {} because canProject is false", projCtx.toHumanReadableString()); + continue; + } + + // we should not get here, but just to be sure + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { + LOGGER.trace("Skipping ignored projection context {}", projCtx.toHumanReadableString()); + continue; + } + + OperationResult subResult = result.subresult(OPERATION_EXECUTE_PROJECTION + "." + projCtx.getObjectTypeClass().getSimpleName()) + .addParam("resource", projCtx.getResource()) + .addArbitraryObjectAsContext("discriminator", projCtx.getResourceShadowDiscriminator()) + .build(); + + PrismObject shadowAfterModification = null; + try { + LOGGER.trace("Executing projection context {}", projCtx.toHumanReadableString()); + + context.checkAbortRequested(); + + context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, + projCtx.getResourceShadowDiscriminator(), ENTERING)); + + executeReconciliationScript(projCtx, context, BeforeAfterType.BEFORE, task, subResult); + + ObjectDelta projDelta = projCtx.getExecutableDelta(); + + if (shouldBeDeleted(projDelta, projCtx)) { + projDelta = prismContext.deltaFactory().object() + .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); + } + + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + if (context.getFocusContext() != null + && context.getFocusContext().getDelta() != null + && context.getFocusContext().getDelta().isDelete() + && context.getOptions() != null + && ModelExecuteOptions.isForce(context.getOptions())) { + if (projDelta == null) { + projDelta = prismContext.deltaFactory().object() + .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); + } + } + if (projDelta != null && projDelta.isDelete()) { + + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, + subResult); + + } + } else { + + if (projDelta == null || projDelta.isEmpty()) { + LOGGER.trace("No change for {}", projCtx.getResourceShadowDiscriminator()); + shadowAfterModification = projCtx.getObjectCurrent(); + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + // Make sure post-reconcile delta is always executed, + // even if there is no change + executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, + subResult); + + subResult.computeStatus(); + subResult.recordNotApplicableIfUnknown(); + continue; + + } else if (projDelta.isDelete() && projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // HACK ... for higher-order context check if this was + // already deleted + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, + projCtx); + if (lowerOrderContext != null && lowerOrderContext.isDelete()) { + // We assume that this was already executed + subResult.setStatus(OperationResultStatus.NOT_APPLICABLE); + continue; + } + } + + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); + + if (projCtx.isAdd() && shadowAfterModification != null) { + projCtx.setExists(true); + } + + } + + subResult.computeStatus(); + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult); + + subResult.computeStatus(); + subResult.recordNotApplicableIfUnknown(); + + } catch (SchemaException | ObjectNotFoundException | PreconditionViolationException | CommunicationException | + ConfigurationException | SecurityViolationException | PolicyViolationException | ExpressionEvaluationException | RuntimeException | Error e) { + recordProjectionExecutionException(e, projCtx, subResult, SynchronizationPolicyDecision.BROKEN); + + // We still want to update the links here. E.g. this may be live sync case where we discovered new account + // try to reconcile, but the reconciliation fails. We still want this shadow linked to user. + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + ModelImplUtils.handleConnectorErrorCriticality(projCtx.getResource(), e, subResult); + + } catch (ObjectAlreadyExistsException e) { + + // This exception is quite special. We have to decide how bad this really is. + // This may be rename conflict. Which would be bad. + // Or this may be attempt to create account that already exists and just needs + // to be linked. Which is no big deal and consistency mechanism (discovery) will + // easily handle that. In that case it is done in "another task" which is + // quasi-asynchornously executed from provisioning by calling notifyChange. + // Once that is done then the account is already linked. And all we need to do + // is to restart this whole operation. + + // check if this is a repeated attempt - OAEE was not handled + // correctly, e.g. if creating "Users" user in AD, whereas + // "Users" is SAM Account Name which is used by a built-in group + // - in such case, mark the context as broken + if (isRepeatedAlreadyExistsException(projCtx)) { + // This is the bad case. Currently we do not do anything more intelligent than to look for + // repeated error. If we get OAEE twice then this is bad and we thow up. + // TODO: do something smarter here + LOGGER.debug("Repeated ObjectAlreadyExistsException detected, marking projection {} as broken", projCtx.toHumanReadableString()); + recordProjectionExecutionException(e, projCtx, subResult, + SynchronizationPolicyDecision.BROKEN); + continue; + } + + // in his case we do not need to set account context as + // broken, instead we need to restart projector for this + // context to recompute new account or find out if the + // account was already linked.. + // and also do not set fatal error to the operation result, this + // is a special case + // if it is fatal, it will be set later + // but we need to set some result + subResult.recordSuccess(); + restartRequested = true; + LOGGER.debug("ObjectAlreadyExistsException for projection {}, requesting projector restart", projCtx.toHumanReadableString()); + // we will process remaining projections when retrying the wave + break; + + } finally { + context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, + projCtx.getResourceShadowDiscriminator(), subResult)); + } + } + + // Result computation here needs to be slightly different + result.computeStatusComposite(); + return restartRequested; + + } catch (Throwable t) { + result.recordThrowableIfNeeded(t); // last resort: to avoid UNKNOWN subresults + throw t; + } + } + + private ObjectDelta applyPendingObjectPolicyStateModifications(LensFocusContext focusContext, + ObjectDelta focusDelta) throws SchemaException { + for (ItemDelta, ?> itemDelta : focusContext.getPendingObjectPolicyStateModifications()) { + focusDelta = focusContext.swallowToDelta(focusDelta, itemDelta); + } + focusContext.clearPendingObjectPolicyStateModifications(); + return focusDelta; + } + + private ObjectDelta applyPendingAssignmentPolicyStateModifications(LensFocusContext focusContext, ObjectDelta focusDelta) + throws SchemaException { + for (Map.Entry>> entry : focusContext + .getPendingAssignmentPolicyStateModifications().entrySet()) { + PlusMinusZero mode = entry.getKey().mode; + if (mode == PlusMinusZero.MINUS) { + continue; // this assignment is being thrown out anyway, so let's ignore it (at least for now) + } + AssignmentType assignmentToFind = entry.getKey().assignment; + List> modifications = entry.getValue(); + if (modifications.isEmpty()) { + continue; + } + LOGGER.trace("Applying policy state modifications for {} ({}):\n{}", assignmentToFind, mode, + DebugUtil.debugDumpLazily(modifications)); + if (mode == PlusMinusZero.ZERO) { + if (assignmentToFind.getId() == null) { + throw new IllegalStateException("Existing assignment with null id: " + assignmentToFind); + } + for (ItemDelta, ?> modification : modifications) { + focusDelta = focusContext.swallowToDelta(focusDelta, modification); + } + } else { + assert mode == PlusMinusZero.PLUS; + if (focusDelta != null && focusDelta.isAdd()) { + swallowIntoValues(((FocusType) focusDelta.getObjectToAdd().asObjectable()).getAssignment(), + assignmentToFind, modifications); + } else { + ContainerDelta assignmentDelta = focusDelta != null ? + focusDelta.findContainerDelta(FocusType.F_ASSIGNMENT) : null; + if (assignmentDelta == null) { + throw new IllegalStateException( + "We have 'plus' assignment to modify but there's no assignment delta. Assignment=" + + assignmentToFind + ", objectDelta=" + focusDelta); + } + if (assignmentDelta.isReplace()) { + swallowIntoValues(asContainerables(assignmentDelta.getValuesToReplace()), assignmentToFind, + modifications); + } else if (assignmentDelta.isAdd()) { + swallowIntoValues(asContainerables(assignmentDelta.getValuesToAdd()), assignmentToFind, + modifications); + } else { + throw new IllegalStateException( + "We have 'plus' assignment to modify but there're no values to add or replace in assignment delta. Assignment=" + + assignmentToFind + ", objectDelta=" + focusDelta); + } + } + } + } + focusContext.clearPendingAssignmentPolicyStateModifications(); + return focusDelta; + } + + private void swallowIntoValues(Collection assignments, AssignmentType assignmentToFind, List> modifications) + throws SchemaException { + for (AssignmentType assignment : assignments) { + PrismContainerValue> pcv = assignment.asPrismContainerValue(); + PrismContainerValue> pcvToFind = assignmentToFind.asPrismContainerValue(); + if (pcv.representsSameValue(pcvToFind, false) || pcv.equals(pcvToFind, EquivalenceStrategy.REAL_VALUE_CONSIDER_DIFFERENT_IDS)) { + // TODO what if ID of the assignment being added is changed in repo? Hopefully it will be not. + for (ItemDelta, ?> modification : modifications) { + ItemPath newParentPath = modification.getParentPath().rest(2); // killing assignment + ID + ItemDelta, ?> pathRelativeModification = modification.cloneWithChangedParentPath(newParentPath); + pathRelativeModification.applyTo(pcv); + } + return; + } + } + // TODO change to warning + throw new IllegalStateException("We have 'plus' assignment to modify but it couldn't be found in assignment delta. Assignment=" + assignmentToFind + ", new assignments=" + assignments); + } + + private void applyLastProvisioningTimestamp(LensContext context, ObjectDelta focusDelta) throws SchemaException { + if (!context.hasProjectionChange()) { + return; + } + if (focusDelta.isAdd()) { + + PrismObject objectToAdd = focusDelta.getObjectToAdd(); + PrismContainer metadataContainer = objectToAdd.findOrCreateContainer(ObjectType.F_METADATA); + metadataContainer.getRealValue().setLastProvisioningTimestamp(clock.currentTimeXMLGregorianCalendar()); + + } else if (focusDelta.isModify()) { + + PropertyDelta provTimestampDelta = prismContext.deltaFactory().property().createModificationReplaceProperty( + ItemPath.create(ObjectType.F_METADATA, MetadataType.F_LAST_PROVISIONING_TIMESTAMP), + context.getFocusContext().getObjectDefinition(), + clock.currentTimeXMLGregorianCalendar()); + focusDelta.addModification(provTimestampDelta); + + } + } + + private boolean shouldBeDeleted(ObjectDelta accDelta, LensProjectionContext accCtx) { + return (accDelta == null || accDelta.isEmpty()) + && (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || accCtx.getSynchronizationIntent() == SynchronizationIntent.DELETE); + } + + private boolean isRepeatedAlreadyExistsException( + LensProjectionContext projContext) { + int deltas = projContext.getExecutedDeltas().size(); + LOGGER.trace("isRepeatedAlreadyExistsException starting; number of executed deltas = {}", deltas); + if (deltas < 2) { + return false; + } + LensObjectDeltaOperation lastDeltaOp = projContext.getExecutedDeltas().get(deltas - 1); + LensObjectDeltaOperation previousDeltaOp = projContext.getExecutedDeltas() + .get(deltas - 2); + // TODO check also previous execution result to see if it's + // AlreadyExistException? + ObjectDelta lastDelta = lastDeltaOp.getObjectDelta(); + ObjectDelta previousDelta = previousDeltaOp.getObjectDelta(); + boolean rv; + if (lastDelta.isAdd() && previousDelta.isAdd()) { + rv = isEquivalentAddDelta(lastDelta.getObjectToAdd(), previousDelta.getObjectToAdd()); + } else if (lastDelta.isModify() && previousDelta.isModify()) { + rv = isEquivalentModifyDelta(lastDelta.getModifications(), previousDelta.getModifications()); + } else { + rv = false; + } + LOGGER.trace( + "isRepeatedAlreadyExistsException returning {}; based of comparison of previousDelta:\n{}\nwith lastDelta:\n{}", + rv, previousDelta, lastDelta); + return rv; + } + + private boolean isEquivalentModifyDelta(Collection extends ItemDelta, ?>> modifications1, + Collection extends ItemDelta, ?>> modifications2) { + Collection extends ItemDelta, ?>> attrDeltas1 = ItemDeltaCollectionsUtil + .findItemDeltasSubPath(modifications1, ShadowType.F_ATTRIBUTES); + Collection extends ItemDelta, ?>> attrDeltas2 = ItemDeltaCollectionsUtil + .findItemDeltasSubPath(modifications2, ShadowType.F_ATTRIBUTES); + //noinspection unchecked,RedundantCast + return MiscUtil.unorderedCollectionEquals((Collection) attrDeltas1, (Collection) attrDeltas2); + } + + private boolean isEquivalentAddDelta(PrismObject object1, PrismObject object2) { + PrismContainer attributes1 = object1.findContainer(ShadowType.F_ATTRIBUTES); + PrismContainer attributes2 = object2.findContainer(ShadowType.F_ATTRIBUTES); + if (attributes1 == null || attributes2 == null || attributes1.size() != 1 + || attributes2.size() != 1) { // suspicious cases + return false; + } + return attributes1.getValue().equivalent(attributes2.getValue()); + } + + private void applyObjectPolicy(LensFocusContext focusContext, + ObjectDelta focusDelta, ArchetypePolicyType archetypePolicy) { + if (archetypePolicy == null) { + return; + } + PrismObject objectNew = focusContext.getObjectNew(); + if (focusDelta.isAdd() && objectNew.getOid() == null) { + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + processItemConstraint(focusContext, objectNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + processItemConstraint(focusContext, objectNew, itemConstraintType); + } + + } + } + + private void processItemConstraint(LensFocusContext focusContext, PrismObject objectNew, ItemConstraintType itemConstraintType) { + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + PrismProperty prop = objectNew.findProperty(itemPath); + String stringValue = prop.getRealValue().toString(); + focusContext.setOid(stringValue); + } + } + + private void recordProjectionExecutionException(Throwable e, + LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) { + subResult.recordFatalError(e); + LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e); + if (decision != null) { + accCtx.setSynchronizationPolicyDecision(decision); + } + } + + private void recordFatalError(OperationResult subResult, OperationResult result, String message, + Throwable e) { + if (message == null) { + message = e.getMessage(); + } + subResult.recordFatalError(e); + if (result != null) { + result.computeStatusComposite(); + } + } + + /** + * Make sure that the account is linked (or unlinked) as needed. + */ + private void updateLinks(LensContext> context, + LensFocusContext focusObjectContext, LensProjectionContext projCtx, + PrismObject shadowAfterModification, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { + if (focusObjectContext == null) { + return; + } + Class objectTypeClass = focusObjectContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(objectTypeClass)) { + return; + } + //noinspection unchecked + LensFocusContext focusContext = (LensFocusContext) focusObjectContext; + + if (projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // Don't mess with links for higher-order contexts. The link should + // be dealt with + // during processing of zero-order context. + return; + } + + String projOid = projCtx.getOid(); + if (projOid == null) { + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + // This seems to be OK. In quite a strange way, but still OK. + return; + } + LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump()); + throw new IllegalStateException("Projection " + projCtx.toHumanReadableString() + " has null OID, this should not happen"); + } + + if (linkShouldExist(focusContext, projCtx, shadowAfterModification, result)) { + // Link should exist + PrismObject objectCurrent = focusContext.getObjectCurrent(); + if (objectCurrent != null) { + for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) { + if (projOid.equals(linkRef.getOid())) { + // Already linked, nothing to do, only be sure, the situation is set with the good value + LOGGER.trace("Updating situation in already linked shadow."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + return; + } + } + } + // Not linked, need to link + linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result); + // be sure, that the situation is set correctly + LOGGER.trace("Updating situation after shadow was linked."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + } else { + // Link should NOT exist + if (!focusContext.isDelete()) { + PrismObject objectCurrent = focusContext.getObjectCurrent(); + // it is possible that objectCurrent is null (and objectNew is + // non-null), in case of User ADD operation (MID-2176) + if (objectCurrent != null) { + PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF); + if (linkRef != null) { + for (PrismReferenceValue linkRefVal : linkRef.getValues()) { + if (linkRefVal.getOid().equals(projOid)) { + // Linked, need to unlink + unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx, task, result); + } + } + } + } + } + + // This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow. + LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid); + updateSituationInShadow(task, null, context, focusObjectContext, projCtx, result); + // Not linked, that's OK + } + } + + private boolean linkShouldExist(LensFocusContext focusContext, LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { + if (focusContext.isDelete()) { + // if we delete focus, link doesn't exist anymore, but be sure, that the situation is updated in shadow + return false; + } + if (!projCtx.isShadowExistsInRepo()) { + // Nothing to link to + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { + return false; + } + if (isEmptyThombstone(projCtx)) { + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || projCtx.isDelete()) { + return shadowAfterModification != null; + } + if (projCtx.hasPendingOperations()) { + return true; + } + return true; + } + + /** + * Return true if this projection is just a linkRef that points to no + * shadow. + */ + private boolean isEmptyThombstone(LensProjectionContext projCtx) { + return projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().isTombstone() + && projCtx.getObjectCurrent() == null; + } + + private void linkShadow(String userOid, String shadowOid, + LensElementContext focusContext, LensProjectionContext projCtx, Task task, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException { + + Class typeClass = focusContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(typeClass)) { + return; + } + + String channel = focusContext.getLensContext().getChannel(); + + LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid); + OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT); + PrismReferenceValue linkRef = prismContext.itemFactory().createReferenceValue(); + linkRef.setOid(shadowOid); + linkRef.setTargetType(ShadowType.COMPLEX_TYPE); + Collection extends ItemDelta> linkRefDeltas = prismContext.deltaFactory().reference() + .createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef); + + try { + cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result); + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, null); + } catch (ObjectAlreadyExistsException ex) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, ex); + throw new SystemException(ex); + } catch (Throwable t) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, t); + throw t; + } finally { + result.computeStatus(); + ObjectDelta
- * Explanation for ADD deltas: there are situations where an execution wave - * is restarted - when unexpected AlreadyExistsException is reported from - * provisioning. However, in such cases, duplicate ADD Focus deltas were - * generated. So we (TEMPORARILY!) decided to filter them out here. - *
- * Unfortunately, this mechanism is not well-defined, and seems to work more - * "by accident" than "by design". It should be replaced with something more - * serious. Perhaps by re-reading current focus state when repeating a wave? - * Actually, it is a supplement for rewriting ADD->MODIFY deltas in - * LensElementContext.getFixedPrimaryDelta. That method converts primary - * deltas (and as far as I know, that is the only place where this problem - * should occur). Nevertheless, for historical and safety reasons I keep - * also the processing in this method. - *
- * Anyway, currently it treats only three cases: - * 1) if the objectDelta is present in the list of executed deltas - * 2) if the objectDelta is ADD, and another ADD delta is there (then the difference is computed) - * 3) if objectDelta is MODIFY or DELETE and previous delta was MODIFY - */ - private ObjectDelta computeDiffDelta( - List extends ObjectDeltaOperation> executedDeltas, ObjectDelta objectDelta) { - if (executedDeltas == null || executedDeltas.isEmpty()) { - return objectDelta; - } - - // any delta related to our OID, not ending with fatal error - ObjectDeltaOperation lastRelated = findLastRelatedDelta(executedDeltas, objectDelta); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("findLastRelatedDelta returned:\n{}", - lastRelated != null ? lastRelated.shorterDebugDump(1) : " (null)"); - } - if (lastRelated == null) { - return objectDelta; // nothing found, let us apply our delta - } - if (lastRelated.getExecutionResult().isSuccess() && lastRelated.containsDelta(objectDelta)) { - return null; // case 1 - exact match found with SUCCESS result, - // let's skip the processing of our delta - } - if (!objectDelta.isAdd()) { - if (lastRelated.getObjectDelta().isDelete()) { - return null; // case 3 - } else { - return objectDelta; // MODIFY or DELETE delta after ADD or MODIFY delta - we may safely apply it - } - } - // determine if we got case 2 - if (lastRelated.getObjectDelta().isDelete()) { - return objectDelta; // we can (and should) apply the ADD delta as a - // whole, because the object was deleted - } - // let us treat the most simple case here - meaning we have existing ADD - // delta and nothing more - // TODO add more sophistication if needed - if (!lastRelated.getObjectDelta().isAdd()) { - return objectDelta; // this will probably fail, but ... - } - // at this point we know that ADD was more-or-less successfully - // executed, so let's compute the difference, creating a MODIFY delta - PrismObject alreadyAdded = lastRelated.getObjectDelta().getObjectToAdd(); - PrismObject toBeAddedNow = objectDelta.getObjectToAdd(); - return alreadyAdded.diff(toBeAddedNow); - } - - private ObjectDeltaOperation findLastRelatedDelta( - List extends ObjectDeltaOperation> executedDeltas, ObjectDelta objectDelta) { - for (int i = executedDeltas.size() - 1; i >= 0; i--) { - ObjectDeltaOperation currentOdo = executedDeltas.get(i); - if (currentOdo.getExecutionResult().isFatalError()) { - continue; - } - ObjectDelta current = currentOdo.getObjectDelta(); - - if (current.equals(objectDelta)) { - return currentOdo; - } - - String oid1 = current.isAdd() ? current.getObjectToAdd().getOid() : current.getOid(); - String oid2 = objectDelta.isAdd() ? objectDelta.getObjectToAdd().getOid() - : objectDelta.getOid(); - if (oid1 != null && oid2 != null) { - if (oid1.equals(oid2)) { - return currentOdo; - } - continue; - } - // ADD-MODIFY and ADD-DELETE combinations lead to applying whole - // delta (as a result of computeDiffDelta) - // so we can be lazy and check only ADD-ADD combinations here... - if (!current.isAdd() || !objectDelta.isAdd()) { - continue; - } - // we simply check the type (for focus objects) and - // resource+kind+intent (for shadows) - PrismObject currentObject = current.getObjectToAdd(); - PrismObject objectTypeToAdd = objectDelta.getObjectToAdd(); - Class currentObjectClass = currentObject.getCompileTimeClass(); - Class objectTypeToAddClass = objectTypeToAdd.getCompileTimeClass(); - if (currentObjectClass == null || !currentObjectClass.equals(objectTypeToAddClass)) { - continue; - } - if (FocusType.class.isAssignableFrom(currentObjectClass)) { - return currentOdo; // we suppose there is only one delta of - // Focus class - } - } - return null; - } - - private ProvisioningOperationOptions copyFromModelOptions(ModelExecuteOptions options) { - ProvisioningOperationOptions provisioningOptions = new ProvisioningOperationOptions(); - if (options == null) { - return provisioningOptions; - } - - provisioningOptions.setForce(options.getForce()); - provisioningOptions.setOverwrite(options.getOverwrite()); - return provisioningOptions; - } - - private ProvisioningOperationOptions getProvisioningOptions(LensContext context, - ModelExecuteOptions modelOptions, PrismObject existingShadow, ObjectDelta delta) throws SecurityViolationException { - if (modelOptions == null && context != null) { - modelOptions = context.getOptions(); - } - ProvisioningOperationOptions provisioningOptions = copyFromModelOptions(modelOptions); - - if (executeAsSelf(context, modelOptions, existingShadow, delta)) { - LOGGER.trace("Setting 'execute as self' provisioning option for {}", existingShadow); - provisioningOptions.setRunAsAccountOid(existingShadow.getOid()); - } - - if (context != null && context.getChannel() != null) { - - if (context.getChannel().equals(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_RECON))) { - // TODO: this is probably wrong. We should not have special case - // for recon channel! This should be handled by the provisioning task - // setting the right options there. - provisioningOptions.setCompletePostponed(false); - } - - if (context.getChannel().equals(SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI)) { - // We want to avoid endless loops in error handling. - provisioningOptions.setDoNotDiscovery(true); - } - } - - return provisioningOptions; - } - - // This is a bit of black magic. We only want to execute as self if there a user is changing its own password - // and we also have old password value. - // Later, this should be improved. Maybe we need special model operation option for this? Or maybe it should be somehow - // automatically detected based on resource capabilities? We do not know yet. Therefore let's do the simplest possible - // thing. Otherwise we might do something that we will later regret. - private boolean executeAsSelf(LensContext context, - ModelExecuteOptions modelOptions, PrismObject existingShadow, ObjectDelta delta) throws SecurityViolationException { - if (existingShadow == null) { - return false; - } - - if (!SchemaConstants.CHANNEL_GUI_SELF_SERVICE_URI.equals(context.getChannel())) { - return false; - } - - if (delta == null) { - return false; - } - if (!delta.isModify()) { - return false; - } - PropertyDelta passwordDelta = delta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - if (passwordDelta == null) { - return false; - } - if (passwordDelta.getEstimatedOldValues() == null || passwordDelta.getEstimatedOldValues().isEmpty()) { - return false; - } - ProtectedStringType oldPassword = passwordDelta.getEstimatedOldValues().iterator().next().getValue(); - if (!oldPassword.canGetCleartext()) { - return false; - } - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return false; - } - if (!focusContext.represents(UserType.class)) { - return false; - } - - MidPointPrincipal principal = securityContextManager.getPrincipal(); - if (principal == null) { - return false; - } - FocusType loggedInUser = principal.getFocus(); - - if (!loggedInUser.getOid().equals(focusContext.getOid())) { - return false; - } - return true; - } - - private void logDeltaExecution(ObjectDelta objectDelta, - LensContext context, ResourceType resource, OperationResult result, Task task) { - StringBuilder sb = new StringBuilder(); - sb.append("---[ "); - if (result == null) { - sb.append("Going to EXECUTE"); - } else { - sb.append("EXECUTED"); - } - sb.append(" delta of ").append(objectDelta.getObjectTypeClass().getSimpleName()); - sb.append(" ]---------------------\n"); - DebugUtil.debugDumpLabel(sb, "Channel", 0); - sb.append(" ").append(LensUtil.getChannel(context, task)).append("\n"); - if (context != null) { - DebugUtil.debugDumpLabel(sb, "Wave", 0); - sb.append(" ").append(context.getExecutionWave()).append("\n"); - } - if (resource != null) { - sb.append("Resource: ").append(resource.toString()).append("\n"); - } - sb.append(objectDelta.debugDump()); - sb.append("\n"); - if (result != null) { - DebugUtil.debugDumpLabel(sb, "Result", 0); - sb.append(" ").append(result.getStatus()).append(": ").append(result.getMessage()); - } - sb.append("\n--------------------------------------------------"); - - LOGGER.debug("\n{}", sb); - } - - private OwnerResolver createOwnerResolver(final LensContext context, Task task, - OperationResult result) { - return new LensOwnerResolver<>(context, objectResolver, task, result); - } - - private PrismObject executeAddition(ObjectDelta change, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - PrismObject objectToAdd = change.getObjectToAdd(); - - for (ItemDelta delta : change.getModifications()) { - delta.applyTo(objectToAdd); - } - change.getModifications().clear(); - - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - T objectTypeToAdd = objectToAdd.asObjectable(); - try { - securityEnforcer.authorize(ModelAuthorizationAction.ADD.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectAdd(objectToAdd), ownerResolver, task, result); - - metadataManager.applyMetadataAdd(context, objectToAdd, clock.currentTimeXMLGregorianCalendar(), task, result); - - if (options == null) { - options = context.getOptions(); - } - - RepoAddOptions addOpt = new RepoAddOptions(); - if (ModelExecuteOptions.isOverwrite(options)) { - addOpt.setOverwrite(true); - } - if (ModelExecuteOptions.isNoCrypt(options)) { - addOpt.setAllowUnencryptedValues(true); - } - - String oid; - if (objectTypeToAdd instanceof TaskType) { - oid = addTask((TaskType) objectTypeToAdd, addOpt, result); - } else if (objectTypeToAdd instanceof NodeType) { - throw new UnsupportedOperationException("NodeType cannot be added using model interface"); - } else if (ObjectTypes.isManagedByProvisioning(objectTypeToAdd)) { - - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) change); - - oid = addProvisioningObject(objectToAdd, context, objectContext, provisioningOptions, - resource, task, result); - if (oid == null) { - throw new SystemException( - "Provisioning addObject returned null OID while adding " + objectToAdd); - } - result.addReturn("createdAccountOid", oid); - } else { - FocusConstraintsChecker.clearCacheFor(objectToAdd.asObjectable().getName()); - - oid = cacheRepositoryService.addObject(objectToAdd, addOpt, result); - if (oid == null) { - throw new SystemException("Repository addObject returned null OID while adding " + objectToAdd); - } - } - if (!change.isImmutable()) { - change.setOid(oid); - } - objectToAdd.setOid(oid); - task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), oid, - ChangeType.ADD, context.getChannel(), null); - return objectToAdd; - } catch (Throwable t) { - task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), null, - ChangeType.ADD, context.getChannel(), t); - if (objectTypeToAdd instanceof ShadowType) { - handleProvisioningError(resource, t, task, result); - ((LensProjectionContext) objectContext).setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - return null; - } - throw t; - - } - } - - private void handleProvisioningError(ResourceType resource, Throwable t, Task task, OperationResult result) throws ObjectNotFoundException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException, CommunicationException, SchemaException { - ErrorSelectorType errorSelectorType = ResourceTypeUtil.getConnectorErrorCriticality(resource); - CriticalityType criticality = ExceptionUtil.getCriticality(errorSelectorType, t, CriticalityType.FATAL); - RepoCommonUtils.processErrorCriticality(task, criticality, t, result); - if (CriticalityType.IGNORE == criticality) { - result.muteLastSubresultError(); - } - } - - private PrismObject executeDeletion(ObjectDelta change, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - ObjectAlreadyExistsException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - - String oid = change.getOid(); - Class objectTypeClass = change.getObjectTypeClass(); - - PrismObject objectOld = objectContext.getObjectOld(); - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - PrismObject objectAfterModification = null; - try { - securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelete(objectOld), ownerResolver, task, result); - - if (TaskType.class.isAssignableFrom(objectTypeClass)) { - taskManager.deleteTask(oid, result); - } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { - taskManager.deleteNode(oid, result); - } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) change); - try { - objectAfterModification = deleteProvisioningObject(objectTypeClass, oid, context, objectContext, - provisioningOptions, resource, task, result); - } catch (ObjectNotFoundException e) { - // Object that we wanted to delete is already gone. This can - // happen in some race conditions. - // As the resulting state is the same as we wanted it to be - // we will not complain and we will go on. - LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid, - objectTypeClass); - result.muteLastSubresultError(); - } - } else { - try { - cacheRepositoryService.deleteObject(objectTypeClass, oid, result); - } catch (ObjectNotFoundException e) { - // Object that we wanted to delete is already gone. This can - // happen in some race conditions. - // As the resulting state is the same as we wanted it to be - // we will not complain and we will go on. - LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid, - objectTypeClass); - result.muteLastSubresultError(); - } - } - task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE, - context.getChannel(), null); - } catch (Throwable t) { - task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE, - context.getChannel(), t); - - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - handleProvisioningError(resource, t, task, result); - return objectContext.getObjectCurrent(); - } - - throw t; - } - - return objectAfterModification; - } - - private void executeModification(ObjectDelta delta, - LensContext context, LensElementContext objectContext, ModelExecuteOptions options, - ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, CommunicationException, - ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException { - Class objectTypeClass = delta.getObjectTypeClass(); - - // We need current object here. The current object is used to get data for id-only container delete deltas, - // replace deltas and so on. The authorization code can figure out new object if needed, but it needs - // current object to start from. - // We cannot use old object here. That would fail in multi-wave executions. We want object that has all the previous - // wave changes already applied. - PrismObject baseObject = objectContext.getObjectCurrent(); - OwnerResolver ownerResolver = createOwnerResolver(context, task, result); - try { - securityEnforcer.authorize(ModelAuthorizationAction.MODIFY.getUrl(), - AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelta(baseObject, delta), ownerResolver, task, result); - - if (shouldApplyModifyMetadata(objectTypeClass, context.getSystemConfigurationType())) { - metadataManager.applyMetadataModify(delta, objectContext, objectTypeClass, - clock.currentTimeXMLGregorianCalendar(), task, context, result); - } - - if (delta.isEmpty()) { - // Nothing to do - return; - } - - if (TaskType.class.isAssignableFrom(objectTypeClass)) { - taskManager.modifyTask(delta.getOid(), delta.getModifications(), result); - } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { - throw new UnsupportedOperationException("NodeType is not modifiable using model interface"); - } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { - ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, - (PrismObject) objectContext.getObjectCurrent(), (ObjectDelta) delta); - String oid = modifyProvisioningObject(objectTypeClass, delta.getOid(), - delta.getModifications(), context, objectContext, provisioningOptions, resource, - task, result); - if (!oid.equals(delta.getOid())) { - delta.setOid(oid); - } - } else { - FocusConstraintsChecker.clearCacheForDelta(delta.getModifications()); - ModificationPrecondition precondition = null; - if (conflictResolution != null) { - String readVersion = objectContext.getObjectReadVersion(); - if (readVersion != null) { - LOGGER.trace("Modification with precondition, readVersion={}", readVersion); - precondition = new VersionPrecondition<>(readVersion); - } else { - LOGGER.warn("Requested careful modification of {}, but there is no read version", objectContext.getHumanReadableName()); - } - } - cacheRepositoryService.modifyObject(objectTypeClass, delta.getOid(), - delta.getModifications(), precondition, null, result); - } - task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY, - context.getChannel(), null); - } catch (Throwable t) { - task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY, - context.getChannel(), t); - throw t; - } - } - - private boolean shouldApplyModifyMetadata(Class objectTypeClass, SystemConfigurationType config) { - if (!ShadowType.class.equals(objectTypeClass)) { - return true; - } else if (config == null || config.getInternals() == null || config.getInternals().getShadowMetadataRecording() == null) { - return true; - } else { - MetadataRecordingStrategyType recording = config.getInternals().getShadowMetadataRecording(); - return !Boolean.TRUE.equals(recording.isSkipOnModify()); - } - } - - private String addTask(TaskType task, RepoAddOptions addOpt, OperationResult result) - throws ObjectAlreadyExistsException { - try { - return taskManager.addTask(task.asPrismObject(), addOpt, result); - } catch (ObjectAlreadyExistsException ex) { - throw ex; - } catch (Exception ex) { - LoggingUtils.logException(LOGGER, "Couldn't add object {} to task manager", ex, task.getName()); - throw new SystemException(ex.getMessage(), ex); - } - } - - private String addProvisioningObject(PrismObject object, - LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - ObjectAlreadyExistsException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException { - - if (object.canRepresent(ShadowType.class)) { - ShadowType shadow = (ShadowType) object.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadow); - if (resourceOid == null) { - throw new IllegalArgumentException("Resource OID is null in shadow"); - } - } - - OperationProvisioningScriptsType scripts = null; - if (object.canRepresent(ShadowType.class)) { - scripts = prepareScripts(object, context, objectContext, ProvisioningOperationTypeType.ADD, - resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - String oid = provisioning.addObject(object, scripts, options, task, result); - ModelImplUtils.clearRequestee(task); - return oid; - } - - private PrismObject deleteProvisioningObject( - Class objectTypeClass, String oid, LensContext context, LensElementContext objectContext, - ProvisioningOperationOptions options, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, PolicyViolationException { - - PrismObject shadowToModify = null; - OperationProvisioningScriptsType scripts = null; - try { - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - shadowToModify = provisioning.getObject(objectTypeClass, oid, - SelectorOptions.createCollection(rootOpts), task, result); - } catch (ObjectNotFoundException ex) { - // this is almost OK, mute the error and try to delete account (it - // will fail if something is wrong) - result.muteLastSubresultError(); - } - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - scripts = prepareScripts(shadowToModify, context, objectContext, - ProvisioningOperationTypeType.DELETE, resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - PrismObject objectAfterModification = provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result); - ModelImplUtils.clearRequestee(task); - return objectAfterModification; - } - - private String modifyProvisioningObject( - Class objectTypeClass, String oid, Collection extends ItemDelta> modifications, - LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, - ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - - PrismObject shadowToModify = null; - OperationProvisioningScriptsType scripts = null; - try { - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - shadowToModify = provisioning.getObject(objectTypeClass, oid, - SelectorOptions.createCollection(rootOpts), task, result); - } catch (ObjectNotFoundException e) { - // We do not want the operation to fail here. The object might have - // been re-created on the resource - // or discovery might re-create it. So simply ignore this error and - // give provisioning a chance to fail - // properly. - result.muteLastSubresultError(); - LOGGER.warn("Repository object {}: {} is gone. But trying to modify resource object anyway", - objectTypeClass, oid); - } - if (ShadowType.class.isAssignableFrom(objectTypeClass)) { - scripts = prepareScripts(shadowToModify, context, objectContext, - ProvisioningOperationTypeType.MODIFY, resource, task, result); - } - ModelImplUtils.setRequestee(task, context); - String changedOid = provisioning.modifyObject(objectTypeClass, oid, modifications, scripts, options, - task, result); - ModelImplUtils.clearRequestee(task); - return changedOid; - } - - private OperationProvisioningScriptsType prepareScripts( - PrismObject changedObject, LensContext context, LensElementContext objectContext, - ProvisioningOperationTypeType operation, ResourceType resource, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing scripts."); - return null; - } - OperationProvisioningScriptsType resourceScripts = resource.getScripts(); - PrismObject resourceObject = (PrismObject) changedObject; - - PrismObject user = null; - if (context.getFocusContext() != null) { - if (context.getFocusContext().getObjectNew() != null) { - user = context.getFocusContext().getObjectNew(); - } else if (context.getFocusContext().getObjectCurrent() != null) { - user = context.getFocusContext().getObjectCurrent(); - } else if (context.getFocusContext().getObjectOld() != null) { - user = context.getFocusContext().getObjectOld(); - } - } - - LensProjectionContext projectionCtx = (LensProjectionContext) objectContext; - PrismObject shadow = null; - if (projectionCtx.getObjectNew() != null) { - shadow = projectionCtx.getObjectNew(); - } else if (projectionCtx.getObjectCurrent() != null) { - shadow = projectionCtx.getObjectCurrent(); - } else { - shadow = projectionCtx.getObjectOld(); - } - - if (shadow == null) { - //put at least something - shadow = resourceObject.clone(); - } - - ResourceShadowDiscriminator discr = ((LensProjectionContext) objectContext) - .getResourceShadowDiscriminator(); - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, discr, - resource.asPrismObject(), context.getSystemConfiguration(), objectContext, prismContext); - // Having delta in provisioning scripts may be very useful. E.g. the script can optimize execution of expensive operations. - variables.put(ExpressionConstants.VAR_DELTA, projectionCtx.getDelta(), ObjectDelta.class); - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, (LensProjectionContext) objectContext, task, result)); - try { - return evaluateScript(resourceScripts, discr, operation, null, variables, expressionProfile, context, objectContext, task, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - } - - private OperationProvisioningScriptsType evaluateScript(OperationProvisioningScriptsType resourceScripts, - ResourceShadowDiscriminator discr, ProvisioningOperationTypeType operation, BeforeAfterType order, - ExpressionVariables variables, ExpressionProfile expressionProfile, LensContext> context, - LensElementContext> objectContext, Task task, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - OperationProvisioningScriptsType outScripts = new OperationProvisioningScriptsType(); - - if (resourceScripts != null) { - OperationProvisioningScriptsType scripts = resourceScripts.clone(); - for (OperationProvisioningScriptType script : scripts.getScript()) { - if (discr != null) { - if (script.getKind() != null && !script.getKind().isEmpty() - && !script.getKind().contains(discr.getKind())) { - continue; - } - if (script.getIntent() != null && !script.getIntent().isEmpty() - && !script.getIntent().contains(discr.getIntent()) && discr.getIntent() != null) { - continue; - } - } - if (operation != null) { - if (!script.getOperation().contains(operation)) { - continue; - } - } - if (order != null) { - if (order != null && order != script.getOrder()) { - continue; - } - } - // Let's do the most expensive evaluation last - if (!evaluateScriptCondition(script, variables, expressionProfile, task, result)) { - continue; - } - for (ProvisioningScriptArgumentType argument : script.getArgument()) { - evaluateScriptArgument(argument, variables, context, objectContext, task, result); - } - outScripts.getScript().add(script); - } - } - - return outScripts; - } - - private boolean evaluateScriptCondition(OperationProvisioningScriptType script, - ExpressionVariables variables, ExpressionProfile expressionProfile, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType condition = script.getCondition(); - if (condition == null) { - return true; - } - - PrismPropertyValue conditionOutput = ExpressionUtil.evaluateCondition(variables, condition, expressionProfile, expressionFactory, " condition for provisioning script ", task, result); - if (conditionOutput == null) { - return true; - } - - Boolean conditionOutputValue = conditionOutput.getValue(); - - return BooleanUtils.isNotFalse(conditionOutputValue); - - } - - private void evaluateScriptArgument(ProvisioningScriptArgumentType argument, - ExpressionVariables variables, LensContext> context, - LensElementContext> objectContext, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - - final QName fakeScriptArgumentName = new QName(SchemaConstants.NS_C, "arg"); - - PrismPropertyDefinition scriptArgumentDefinition = prismContext.definitionFactory().createPropertyDefinition( - fakeScriptArgumentName, DOMUtil.XSD_STRING); - - String shortDesc = "Provisioning script argument expression"; - Expression, PrismPropertyDefinition> expression = expressionFactory - .makeExpression(argument, scriptArgumentDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); - ExpressionEnvironment, ?, ?> env = new ExpressionEnvironment<>(context, - objectContext instanceof LensProjectionContext ? (LensProjectionContext) objectContext : null, task, result); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, env, result); - - Collection> nonNegativeValues = null; - if (outputTriple != null) { - nonNegativeValues = outputTriple.getNonNegativeValues(); - } - - // replace dynamic script with static value.. - XNodeFactory factory = prismContext.xnodeFactory(); - - argument.getExpressionEvaluator().clear(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - // We need to create at least one evaluator. Otherwise the - // expression code will complain - JAXBElement el = new JAXBElement<>(SchemaConstants.C_VALUE, RawType.class, new RawType(prismContext)); - argument.getExpressionEvaluator().add(el); - - } else { - for (PrismPropertyValue val : nonNegativeValues) { - PrimitiveXNode prim = factory.primitive(val.getValue(), DOMUtil.XSD_STRING); - JAXBElement el = new JAXBElement<>(SchemaConstants.C_VALUE, RawType.class, new RawType(prim, prismContext)); - argument.getExpressionEvaluator().add(el); - } - } - } - - private void executeReconciliationScript( - LensProjectionContext projContext, LensContext context, BeforeAfterType order, Task task, - OperationResult parentResult) throws SchemaException, ObjectNotFoundException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectAlreadyExistsException { - - if (!projContext.isDoReconciliation()) { - return; - } - - ResourceType resource = projContext.getResource(); - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts."); - return; - } - - OperationProvisioningScriptsType resourceScripts = resource.getScripts(); - if (resourceScripts == null) { - return; - } - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - executeProvisioningScripts(context, projContext, resourceScripts, ProvisioningOperationTypeType.RECONCILE, order, expressionProfile, task, parentResult); - } - - private Object executeProvisioningScripts(LensContext context, LensProjectionContext projContext, - OperationProvisioningScriptsType scripts, ProvisioningOperationTypeType operation, BeforeAfterType order, ExpressionProfile expressionProfile, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException, ObjectAlreadyExistsException { - - ResourceType resource = projContext.getResource(); - if (resource == null) { - LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts."); - return null; - } - - PrismObject user = null; - PrismObject shadow = null; - - if (context.getFocusContext() != null) { - if (context.getFocusContext().getObjectNew() != null) { - user = context.getFocusContext().getObjectNew(); - } else if (context.getFocusContext().getObjectOld() != null) { - user = context.getFocusContext().getObjectOld(); - } - // if (order == ProvisioningScriptOrderType.BEFORE) { - // user = context.getFocusContext().getObjectOld(); - // } else if (order == ProvisioningScriptOrderType.AFTER) { - // user = context.getFocusContext().getObjectNew(); - // } else { - // throw new IllegalArgumentException("Unknown order "+order); - // } - } - - if (order == BeforeAfterType.BEFORE) { - shadow = (PrismObject) projContext.getObjectOld(); - } else if (order == BeforeAfterType.AFTER) { - shadow = (PrismObject) projContext.getObjectNew(); - } else { - shadow = (PrismObject) projContext.getObjectCurrent(); - } - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, - projContext.getResourceShadowDiscriminator(), resource.asPrismObject(), - context.getSystemConfiguration(), projContext, prismContext); - Object scriptResult = null; - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, projContext, task, parentResult)); - try { - OperationProvisioningScriptsType evaluatedScript = evaluateScript(scripts, - projContext.getResourceShadowDiscriminator(), operation, order, - variables, expressionProfile, context, projContext, task, parentResult); - for (OperationProvisioningScriptType script : evaluatedScript.getScript()) { - ModelImplUtils.setRequestee(task, context); - scriptResult = provisioning.executeScript(resource.getOid(), script, task, parentResult); - ModelImplUtils.clearRequestee(task); - } - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - return scriptResult; - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.FOCUS_OPERATION; +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.RESOURCE_OBJECT_OPERATION; +import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; +import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerables; +import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.xml.bind.JAXBElement; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import org.apache.commons.lang.BooleanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.common.SynchronizationUtils; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.ProgressInformation; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; +import com.evolveum.midpoint.prism.xnode.XNodeFactory; +import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.api.*; +import com.evolveum.midpoint.repo.common.expression.*; +import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.OwnerResolver; +import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import com.evolveum.prism.xml.ns._public.types_3.RawType; + +/** + * @author semancik + */ +@Component +public class ChangeExecutor { + + private static final Trace LOGGER = TraceManager.getTrace(ChangeExecutor.class); + + private static final String OPERATION_EXECUTE_DELTA = ChangeExecutor.class.getName() + ".executeDelta"; + private static final String OPERATION_EXECUTE = ChangeExecutor.class.getName() + ".execute"; + private static final String OPERATION_EXECUTE_FOCUS = OPERATION_EXECUTE + ".focus"; + private static final String OPERATION_EXECUTE_PROJECTION = OPERATION_EXECUTE + ".projection"; + private static final String OPERATION_LINK_ACCOUNT = ChangeExecutor.class.getName() + ".linkShadow"; + private static final String OPERATION_UNLINK_ACCOUNT = ChangeExecutor.class.getName() + ".unlinkShadow"; + private static final String OPERATION_UPDATE_SITUATION_IN_SHADOW = ChangeExecutor.class.getName() + ".updateSituationInShadow"; + + @Autowired private transient TaskManager taskManager; + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; + @Autowired private ProvisioningService provisioning; + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private SecurityEnforcer securityEnforcer; + @Autowired private SecurityContextManager securityContextManager; + @Autowired private Clock clock; + @Autowired private ModelObjectResolver objectResolver; + @Autowired private OperationalDataManager metadataManager; + @Autowired private CredentialsProcessor credentialsProcessor; + + private PrismObjectDefinition userDefinition = null; + private PrismObjectDefinition shadowDefinition = null; + + @PostConstruct + private void locateDefinitions() { + userDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(UserType.class); + shadowDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(ShadowType.class); + } + + // returns true if current operation has to be restarted, see + // ObjectAlreadyExistsException handling (TODO specify more exactly) + public boolean executeChanges(LensContext context, Task task, + OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException, PreconditionViolationException, PolicyViolationException { + + OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE); + + try { + + // FOCUS + + context.checkAbortRequested(); + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + ObjectDelta focusDelta = focusContext.getWaveExecutableDelta(context.getExecutionWave()); + + focusDelta = applyPendingObjectPolicyStateModifications(focusContext, focusDelta); + focusDelta = applyPendingAssignmentPolicyStateModifications(focusContext, focusDelta); + + if (focusDelta == null && !context.hasProjectionChange()) { + LOGGER.trace("Skipping focus change execute, because user delta is null"); + } else { + + if (focusDelta == null) { + focusDelta = focusContext.getObjectAny().createModifyDelta(); + } + + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + applyObjectPolicy(focusContext, focusDelta, archetypePolicy); + + OperationResult subResult = result.createSubresult( + OPERATION_EXECUTE_FOCUS + "." + focusContext.getObjectTypeClass().getSimpleName()); + + try { + // Will remove credential deltas or hash them + focusDelta = credentialsProcessor.transformFocusExecutionDelta(context, focusDelta); + } catch (EncryptionException e) { + recordFatalError(subResult, result, null, e); + result.computeStatus(); + throw new SystemException(e.getMessage(), e); + } + + applyLastProvisioningTimestamp(context, focusDelta); + + try { + + context.reportProgress(new ProgressInformation(FOCUS_OPERATION, ENTERING)); + + ConflictResolutionType conflictResolution = ModelExecuteOptions + .getFocusConflictResolution(context.getOptions()); + + executeDelta(focusDelta, focusContext, context, null, conflictResolution, null, task, subResult); + + if (focusDelta.isAdd() && focusDelta.getOid() != null) { + // The watcher can already exist; if the OID was pre-existing in the object. + if (context.getFocusConflictWatcher() == null) { + ConflictWatcher watcher = context + .createAndRegisterFocusConflictWatcher(focusDelta.getOid(), cacheRepositoryService); + watcher.setExpectedVersion(focusDelta.getObjectToAdd().getVersion()); + } + } + subResult.computeStatus(); + + } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException | RuntimeException e) { + recordFatalError(subResult, result, null, e); + throw e; + + } catch (PreconditionViolationException e) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Modification precondition failed for {}: {}", focusContext.getHumanReadableName(), + e.getMessage()); + } + // TODO: fatal error if the conflict resolution is "error" (later) + result.recordHandledError(e); + throw e; + + } catch (ObjectAlreadyExistsException e) { + subResult.computeStatus(); + if (!subResult.isSuccess() && !subResult.isHandledError()) { + subResult.recordFatalError(e); + } + result.computeStatusComposite(); + throw e; + } finally { + context.reportProgress(new ProgressInformation(FOCUS_OPERATION, subResult)); + } + } + } + + // PROJECTIONS + + context.checkAbortRequested(); + + boolean restartRequested = false; + + for (LensProjectionContext projCtx : context.getProjectionContexts()) { + if (projCtx.getWave() != context.getExecutionWave()) { + LOGGER.trace("Skipping projection context {} because its wave ({}) is different from execution wave ({})", + projCtx.toHumanReadableString(), projCtx.getWave(), context.getExecutionWave()); + continue; + } + + if (!projCtx.isCanProject()) { + LOGGER.trace("Skipping projection context {} because canProject is false", projCtx.toHumanReadableString()); + continue; + } + + // we should not get here, but just to be sure + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { + LOGGER.trace("Skipping ignored projection context {}", projCtx.toHumanReadableString()); + continue; + } + + OperationResult subResult = result.subresult(OPERATION_EXECUTE_PROJECTION + "." + projCtx.getObjectTypeClass().getSimpleName()) + .addParam("resource", projCtx.getResource()) + .addArbitraryObjectAsContext("discriminator", projCtx.getResourceShadowDiscriminator()) + .build(); + + PrismObject shadowAfterModification = null; + try { + LOGGER.trace("Executing projection context {}", projCtx.toHumanReadableString()); + + context.checkAbortRequested(); + + context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, + projCtx.getResourceShadowDiscriminator(), ENTERING)); + + executeReconciliationScript(projCtx, context, BeforeAfterType.BEFORE, task, subResult); + + ObjectDelta projDelta = projCtx.getExecutableDelta(); + + if (shouldBeDeleted(projDelta, projCtx)) { + projDelta = prismContext.deltaFactory().object() + .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); + } + + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + if (context.getFocusContext() != null + && context.getFocusContext().getDelta() != null + && context.getFocusContext().getDelta().isDelete() + && context.getOptions() != null + && ModelExecuteOptions.isForce(context.getOptions())) { + if (projDelta == null) { + projDelta = prismContext.deltaFactory().object() + .createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid()); + } + } + if (projDelta != null && projDelta.isDelete()) { + + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, + subResult); + + } + } else { + + if (projDelta == null || projDelta.isEmpty()) { + LOGGER.trace("No change for {}", projCtx.getResourceShadowDiscriminator()); + shadowAfterModification = projCtx.getObjectCurrent(); + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + // Make sure post-reconcile delta is always executed, + // even if there is no change + executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, + subResult); + + subResult.computeStatus(); + subResult.recordNotApplicableIfUnknown(); + continue; + + } else if (projDelta.isDelete() && projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // HACK ... for higher-order context check if this was + // already deleted + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, + projCtx); + if (lowerOrderContext != null && lowerOrderContext.isDelete()) { + // We assume that this was already executed + subResult.setStatus(OperationResultStatus.NOT_APPLICABLE); + continue; + } + } + + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); + + if (projCtx.isAdd() && shadowAfterModification != null) { + projCtx.setExists(true); + } + + } + + subResult.computeStatus(); + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult); + + subResult.computeStatus(); + subResult.recordNotApplicableIfUnknown(); + + } catch (SchemaException | ObjectNotFoundException | PreconditionViolationException | CommunicationException | + ConfigurationException | SecurityViolationException | PolicyViolationException | ExpressionEvaluationException | RuntimeException | Error e) { + recordProjectionExecutionException(e, projCtx, subResult, SynchronizationPolicyDecision.BROKEN); + + // We still want to update the links here. E.g. this may be live sync case where we discovered new account + // try to reconcile, but the reconciliation fails. We still want this shadow linked to user. + if (focusContext != null) { + updateLinks(context, focusContext, projCtx, shadowAfterModification, task, subResult); + } + + ModelImplUtils.handleConnectorErrorCriticality(projCtx.getResource(), e, subResult); + + } catch (ObjectAlreadyExistsException e) { + + // This exception is quite special. We have to decide how bad this really is. + // This may be rename conflict. Which would be bad. + // Or this may be attempt to create account that already exists and just needs + // to be linked. Which is no big deal and consistency mechanism (discovery) will + // easily handle that. In that case it is done in "another task" which is + // quasi-asynchornously executed from provisioning by calling notifyChange. + // Once that is done then the account is already linked. And all we need to do + // is to restart this whole operation. + + // check if this is a repeated attempt - OAEE was not handled + // correctly, e.g. if creating "Users" user in AD, whereas + // "Users" is SAM Account Name which is used by a built-in group + // - in such case, mark the context as broken + if (isRepeatedAlreadyExistsException(projCtx)) { + // This is the bad case. Currently we do not do anything more intelligent than to look for + // repeated error. If we get OAEE twice then this is bad and we thow up. + // TODO: do something smarter here + LOGGER.debug("Repeated ObjectAlreadyExistsException detected, marking projection {} as broken", projCtx.toHumanReadableString()); + recordProjectionExecutionException(e, projCtx, subResult, + SynchronizationPolicyDecision.BROKEN); + continue; + } + + // in his case we do not need to set account context as + // broken, instead we need to restart projector for this + // context to recompute new account or find out if the + // account was already linked.. + // and also do not set fatal error to the operation result, this + // is a special case + // if it is fatal, it will be set later + // but we need to set some result + subResult.recordSuccess(); + restartRequested = true; + LOGGER.debug("ObjectAlreadyExistsException for projection {}, requesting projector restart", projCtx.toHumanReadableString()); + // we will process remaining projections when retrying the wave + break; + + } finally { + context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION, + projCtx.getResourceShadowDiscriminator(), subResult)); + } + } + + // Result computation here needs to be slightly different + result.computeStatusComposite(); + return restartRequested; + + } catch (Throwable t) { + result.recordThrowableIfNeeded(t); // last resort: to avoid UNKNOWN subresults + throw t; + } + } + + private ObjectDelta applyPendingObjectPolicyStateModifications(LensFocusContext focusContext, + ObjectDelta focusDelta) throws SchemaException { + for (ItemDelta, ?> itemDelta : focusContext.getPendingObjectPolicyStateModifications()) { + focusDelta = focusContext.swallowToDelta(focusDelta, itemDelta); + } + focusContext.clearPendingObjectPolicyStateModifications(); + return focusDelta; + } + + private ObjectDelta applyPendingAssignmentPolicyStateModifications(LensFocusContext focusContext, ObjectDelta focusDelta) + throws SchemaException { + for (Map.Entry>> entry : focusContext + .getPendingAssignmentPolicyStateModifications().entrySet()) { + PlusMinusZero mode = entry.getKey().mode; + if (mode == PlusMinusZero.MINUS) { + continue; // this assignment is being thrown out anyway, so let's ignore it (at least for now) + } + AssignmentType assignmentToFind = entry.getKey().assignment; + List> modifications = entry.getValue(); + if (modifications.isEmpty()) { + continue; + } + LOGGER.trace("Applying policy state modifications for {} ({}):\n{}", assignmentToFind, mode, + DebugUtil.debugDumpLazily(modifications)); + if (mode == PlusMinusZero.ZERO) { + if (assignmentToFind.getId() == null) { + throw new IllegalStateException("Existing assignment with null id: " + assignmentToFind); + } + for (ItemDelta, ?> modification : modifications) { + focusDelta = focusContext.swallowToDelta(focusDelta, modification); + } + } else { + assert mode == PlusMinusZero.PLUS; + if (focusDelta != null && focusDelta.isAdd()) { + swallowIntoValues(((FocusType) focusDelta.getObjectToAdd().asObjectable()).getAssignment(), + assignmentToFind, modifications); + } else { + ContainerDelta assignmentDelta = focusDelta != null ? + focusDelta.findContainerDelta(FocusType.F_ASSIGNMENT) : null; + if (assignmentDelta == null) { + throw new IllegalStateException( + "We have 'plus' assignment to modify but there's no assignment delta. Assignment=" + + assignmentToFind + ", objectDelta=" + focusDelta); + } + if (assignmentDelta.isReplace()) { + swallowIntoValues(asContainerables(assignmentDelta.getValuesToReplace()), assignmentToFind, + modifications); + } else if (assignmentDelta.isAdd()) { + swallowIntoValues(asContainerables(assignmentDelta.getValuesToAdd()), assignmentToFind, + modifications); + } else { + throw new IllegalStateException( + "We have 'plus' assignment to modify but there're no values to add or replace in assignment delta. Assignment=" + + assignmentToFind + ", objectDelta=" + focusDelta); + } + } + } + } + focusContext.clearPendingAssignmentPolicyStateModifications(); + return focusDelta; + } + + private void swallowIntoValues(Collection assignments, AssignmentType assignmentToFind, List> modifications) + throws SchemaException { + for (AssignmentType assignment : assignments) { + PrismContainerValue> pcv = assignment.asPrismContainerValue(); + PrismContainerValue> pcvToFind = assignmentToFind.asPrismContainerValue(); + if (pcv.representsSameValue(pcvToFind, false) || pcv.equals(pcvToFind, EquivalenceStrategy.REAL_VALUE_CONSIDER_DIFFERENT_IDS)) { + // TODO what if ID of the assignment being added is changed in repo? Hopefully it will be not. + for (ItemDelta, ?> modification : modifications) { + ItemPath newParentPath = modification.getParentPath().rest(2); // killing assignment + ID + ItemDelta, ?> pathRelativeModification = modification.cloneWithChangedParentPath(newParentPath); + pathRelativeModification.applyTo(pcv); + } + return; + } + } + // TODO change to warning + throw new IllegalStateException("We have 'plus' assignment to modify but it couldn't be found in assignment delta. Assignment=" + assignmentToFind + ", new assignments=" + assignments); + } + + private void applyLastProvisioningTimestamp(LensContext context, ObjectDelta focusDelta) throws SchemaException { + if (!context.hasProjectionChange()) { + return; + } + if (focusDelta.isAdd()) { + + PrismObject objectToAdd = focusDelta.getObjectToAdd(); + PrismContainer metadataContainer = objectToAdd.findOrCreateContainer(ObjectType.F_METADATA); + metadataContainer.getRealValue().setLastProvisioningTimestamp(clock.currentTimeXMLGregorianCalendar()); + + } else if (focusDelta.isModify()) { + + PropertyDelta provTimestampDelta = prismContext.deltaFactory().property().createModificationReplaceProperty( + ItemPath.create(ObjectType.F_METADATA, MetadataType.F_LAST_PROVISIONING_TIMESTAMP), + context.getFocusContext().getObjectDefinition(), + clock.currentTimeXMLGregorianCalendar()); + focusDelta.addModification(provTimestampDelta); + + } + } + + private boolean shouldBeDeleted(ObjectDelta accDelta, LensProjectionContext accCtx) { + return (accDelta == null || accDelta.isEmpty()) + && (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || accCtx.getSynchronizationIntent() == SynchronizationIntent.DELETE); + } + + private boolean isRepeatedAlreadyExistsException( + LensProjectionContext projContext) { + int deltas = projContext.getExecutedDeltas().size(); + LOGGER.trace("isRepeatedAlreadyExistsException starting; number of executed deltas = {}", deltas); + if (deltas < 2) { + return false; + } + LensObjectDeltaOperation lastDeltaOp = projContext.getExecutedDeltas().get(deltas - 1); + LensObjectDeltaOperation previousDeltaOp = projContext.getExecutedDeltas() + .get(deltas - 2); + // TODO check also previous execution result to see if it's + // AlreadyExistException? + ObjectDelta lastDelta = lastDeltaOp.getObjectDelta(); + ObjectDelta previousDelta = previousDeltaOp.getObjectDelta(); + boolean rv; + if (lastDelta.isAdd() && previousDelta.isAdd()) { + rv = isEquivalentAddDelta(lastDelta.getObjectToAdd(), previousDelta.getObjectToAdd()); + } else if (lastDelta.isModify() && previousDelta.isModify()) { + rv = isEquivalentModifyDelta(lastDelta.getModifications(), previousDelta.getModifications()); + } else { + rv = false; + } + LOGGER.trace( + "isRepeatedAlreadyExistsException returning {}; based of comparison of previousDelta:\n{}\nwith lastDelta:\n{}", + rv, previousDelta, lastDelta); + return rv; + } + + private boolean isEquivalentModifyDelta(Collection extends ItemDelta, ?>> modifications1, + Collection extends ItemDelta, ?>> modifications2) { + Collection extends ItemDelta, ?>> attrDeltas1 = ItemDeltaCollectionsUtil + .findItemDeltasSubPath(modifications1, ShadowType.F_ATTRIBUTES); + Collection extends ItemDelta, ?>> attrDeltas2 = ItemDeltaCollectionsUtil + .findItemDeltasSubPath(modifications2, ShadowType.F_ATTRIBUTES); + //noinspection unchecked,RedundantCast + return MiscUtil.unorderedCollectionEquals((Collection) attrDeltas1, (Collection) attrDeltas2); + } + + private boolean isEquivalentAddDelta(PrismObject object1, PrismObject object2) { + PrismContainer attributes1 = object1.findContainer(ShadowType.F_ATTRIBUTES); + PrismContainer attributes2 = object2.findContainer(ShadowType.F_ATTRIBUTES); + if (attributes1 == null || attributes2 == null || attributes1.size() != 1 + || attributes2.size() != 1) { // suspicious cases + return false; + } + return attributes1.getValue().equivalent(attributes2.getValue()); + } + + private void applyObjectPolicy(LensFocusContext focusContext, + ObjectDelta focusDelta, ArchetypePolicyType archetypePolicy) { + if (archetypePolicy == null) { + return; + } + PrismObject objectNew = focusContext.getObjectNew(); + if (focusDelta.isAdd() && objectNew.getOid() == null) { + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + processItemConstraint(focusContext, objectNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + processItemConstraint(focusContext, objectNew, itemConstraintType); + } + + } + } + + private void processItemConstraint(LensFocusContext focusContext, PrismObject objectNew, ItemConstraintType itemConstraintType) { + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + PrismProperty prop = objectNew.findProperty(itemPath); + String stringValue = prop.getRealValue().toString(); + focusContext.setOid(stringValue); + } + } + + private void recordProjectionExecutionException(Throwable e, + LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) { + subResult.recordFatalError(e); + LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e); + if (decision != null) { + accCtx.setSynchronizationPolicyDecision(decision); + } + } + + private void recordFatalError(OperationResult subResult, OperationResult result, String message, + Throwable e) { + if (message == null) { + message = e.getMessage(); + } + subResult.recordFatalError(e); + if (result != null) { + result.computeStatusComposite(); + } + } + + /** + * Make sure that the account is linked (or unlinked) as needed. + */ + private void updateLinks(LensContext> context, + LensFocusContext focusObjectContext, LensProjectionContext projCtx, + PrismObject shadowAfterModification, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { + if (focusObjectContext == null) { + return; + } + Class objectTypeClass = focusObjectContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(objectTypeClass)) { + return; + } + //noinspection unchecked + LensFocusContext focusContext = (LensFocusContext) focusObjectContext; + + if (projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // Don't mess with links for higher-order contexts. The link should + // be dealt with + // during processing of zero-order context. + return; + } + + String projOid = projCtx.getOid(); + if (projOid == null) { + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + // This seems to be OK. In quite a strange way, but still OK. + return; + } + LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump()); + throw new IllegalStateException("Projection " + projCtx.toHumanReadableString() + " has null OID, this should not happen"); + } + + if (linkShouldExist(focusContext, projCtx, shadowAfterModification, result)) { + // Link should exist + PrismObject objectCurrent = focusContext.getObjectCurrent(); + if (objectCurrent != null) { + for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) { + if (projOid.equals(linkRef.getOid())) { + // Already linked, nothing to do, only be sure, the situation is set with the good value + LOGGER.trace("Updating situation in already linked shadow."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + return; + } + } + } + // Not linked, need to link + linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result); + // be sure, that the situation is set correctly + LOGGER.trace("Updating situation after shadow was linked."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + } else { + // Link should NOT exist + if (!focusContext.isDelete()) { + PrismObject objectCurrent = focusContext.getObjectCurrent(); + // it is possible that objectCurrent is null (and objectNew is + // non-null), in case of User ADD operation (MID-2176) + if (objectCurrent != null) { + PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF); + if (linkRef != null) { + for (PrismReferenceValue linkRefVal : linkRef.getValues()) { + if (linkRefVal.getOid().equals(projOid)) { + // Linked, need to unlink + unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx, task, result); + } + } + } + } + } + + // This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow. + LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid); + updateSituationInShadow(task, null, context, focusObjectContext, projCtx, result); + // Not linked, that's OK + } + } + + private boolean linkShouldExist(LensFocusContext focusContext, LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { + if (focusContext.isDelete()) { + // if we delete focus, link doesn't exist anymore, but be sure, that the situation is updated in shadow + return false; + } + if (!projCtx.isShadowExistsInRepo()) { + // Nothing to link to + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { + return false; + } + if (isEmptyThombstone(projCtx)) { + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || projCtx.isDelete()) { + return shadowAfterModification != null; + } + if (projCtx.hasPendingOperations()) { + return true; + } + return true; + } + + /** + * Return true if this projection is just a linkRef that points to no + * shadow. + */ + private boolean isEmptyThombstone(LensProjectionContext projCtx) { + return projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().isTombstone() + && projCtx.getObjectCurrent() == null; + } + + private void linkShadow(String userOid, String shadowOid, + LensElementContext focusContext, LensProjectionContext projCtx, Task task, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException { + + Class typeClass = focusContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(typeClass)) { + return; + } + + String channel = focusContext.getLensContext().getChannel(); + + LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid); + OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT); + PrismReferenceValue linkRef = prismContext.itemFactory().createReferenceValue(); + linkRef.setOid(shadowOid); + linkRef.setTargetType(ShadowType.COMPLEX_TYPE); + Collection extends ItemDelta> linkRefDeltas = prismContext.deltaFactory().reference() + .createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef); + + try { + cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result); + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, null); + } catch (ObjectAlreadyExistsException ex) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, ex); + throw new SystemException(ex); + } catch (Throwable t) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, t); + throw t; + } finally { + result.computeStatus(); + ObjectDelta
void recordProjectionExecutionException(Throwable e, + LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) { + subResult.recordFatalError(e); + LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e); + if (decision != null) { + accCtx.setSynchronizationPolicyDecision(decision); + } + } + + private void recordFatalError(OperationResult subResult, OperationResult result, String message, + Throwable e) { + if (message == null) { + message = e.getMessage(); + } + subResult.recordFatalError(e); + if (result != null) { + result.computeStatusComposite(); + } + } + + /** + * Make sure that the account is linked (or unlinked) as needed. + */ + private void updateLinks(LensContext> context, + LensFocusContext focusObjectContext, LensProjectionContext projCtx, + PrismObject shadowAfterModification, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { + if (focusObjectContext == null) { + return; + } + Class objectTypeClass = focusObjectContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(objectTypeClass)) { + return; + } + //noinspection unchecked + LensFocusContext focusContext = (LensFocusContext) focusObjectContext; + + if (projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().getOrder() > 0) { + // Don't mess with links for higher-order contexts. The link should + // be dealt with + // during processing of zero-order context. + return; + } + + String projOid = projCtx.getOid(); + if (projOid == null) { + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + // This seems to be OK. In quite a strange way, but still OK. + return; + } + LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump()); + throw new IllegalStateException("Projection " + projCtx.toHumanReadableString() + " has null OID, this should not happen"); + } + + if (linkShouldExist(focusContext, projCtx, shadowAfterModification, result)) { + // Link should exist + PrismObject objectCurrent = focusContext.getObjectCurrent(); + if (objectCurrent != null) { + for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) { + if (projOid.equals(linkRef.getOid())) { + // Already linked, nothing to do, only be sure, the situation is set with the good value + LOGGER.trace("Updating situation in already linked shadow."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + return; + } + } + } + // Not linked, need to link + linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result); + // be sure, that the situation is set correctly + LOGGER.trace("Updating situation after shadow was linked."); + updateSituationInShadow(task, SynchronizationSituationType.LINKED, context, focusObjectContext, projCtx, result); + } else { + // Link should NOT exist + if (!focusContext.isDelete()) { + PrismObject objectCurrent = focusContext.getObjectCurrent(); + // it is possible that objectCurrent is null (and objectNew is + // non-null), in case of User ADD operation (MID-2176) + if (objectCurrent != null) { + PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF); + if (linkRef != null) { + for (PrismReferenceValue linkRefVal : linkRef.getValues()) { + if (linkRefVal.getOid().equals(projOid)) { + // Linked, need to unlink + unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx, task, result); + } + } + } + } + } + + // This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow. + LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid); + updateSituationInShadow(task, null, context, focusObjectContext, projCtx, result); + // Not linked, that's OK + } + } + + private boolean linkShouldExist(LensFocusContext focusContext, LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { + if (focusContext.isDelete()) { + // if we delete focus, link doesn't exist anymore, but be sure, that the situation is updated in shadow + return false; + } + if (!projCtx.isShadowExistsInRepo()) { + // Nothing to link to + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { + return false; + } + if (isEmptyThombstone(projCtx)) { + return false; + } + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE + || projCtx.isDelete()) { + return shadowAfterModification != null; + } + if (projCtx.hasPendingOperations()) { + return true; + } + return true; + } + + /** + * Return true if this projection is just a linkRef that points to no + * shadow. + */ + private boolean isEmptyThombstone(LensProjectionContext projCtx) { + return projCtx.getResourceShadowDiscriminator() != null + && projCtx.getResourceShadowDiscriminator().isTombstone() + && projCtx.getObjectCurrent() == null; + } + + private void linkShadow(String userOid, String shadowOid, + LensElementContext focusContext, LensProjectionContext projCtx, Task task, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException { + + Class typeClass = focusContext.getObjectTypeClass(); + if (!FocusType.class.isAssignableFrom(typeClass)) { + return; + } + + String channel = focusContext.getLensContext().getChannel(); + + LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid); + OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT); + PrismReferenceValue linkRef = prismContext.itemFactory().createReferenceValue(); + linkRef.setOid(shadowOid); + linkRef.setTargetType(ShadowType.COMPLEX_TYPE); + Collection extends ItemDelta> linkRefDeltas = prismContext.deltaFactory().reference() + .createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef); + + try { + cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result); + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, null); + } catch (ObjectAlreadyExistsException ex) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, ex); + throw new SystemException(ex); + } catch (Throwable t) { + task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid, + ChangeType.MODIFY, channel, t); + throw t; + } finally { + result.computeStatus(); + ObjectDelta