You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2017/06/06 13:06:28 UTC
[02/10] syncope git commit: [SYNCOPE-1103] Option to put Quartz
scheduler in standby, from provisioning.properties
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
new file mode 100644
index 0000000..0d37d86
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/NotificationJobDelegate.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.notification;
+
+import java.util.Date;
+import java.util.Properties;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.event.AfterHandlingEvent;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class NotificationJobDelegate {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NotificationJobDelegate.class);
+
+ /**
+ * Task DAO.
+ */
+ @Autowired
+ private TaskDAO taskDAO;
+
+ @Autowired
+ private JavaMailSender mailSender;
+
+ @Autowired
+ private EntityFactory entityFactory;
+
+ @Autowired
+ private AuditManager auditManager;
+
+ @Autowired
+ private NotificationManager notificationManager;
+
+ @Autowired
+ private ApplicationEventPublisher publisher;
+
+ private long maxRetries;
+
+ private void init() {
+ maxRetries = notificationManager.getMaxRetries();
+
+ if (mailSender instanceof JavaMailSenderImpl
+ && StringUtils.isNotBlank(((JavaMailSenderImpl) mailSender).getUsername())) {
+
+ Properties javaMailProperties = ((JavaMailSenderImpl) mailSender).getJavaMailProperties();
+ javaMailProperties.setProperty("mail.smtp.auth", "true");
+ ((JavaMailSenderImpl) mailSender).setJavaMailProperties(javaMailProperties);
+ }
+ }
+
+ @Transactional
+ public TaskExec executeSingle(final NotificationTask task) {
+ init();
+
+ TaskExec execution = entityFactory.newEntity(TaskExec.class);
+ execution.setTask(task);
+ execution.setStart(new Date());
+
+ boolean retryPossible = true;
+
+ if (StringUtils.isBlank(task.getSubject()) || task.getRecipients().isEmpty()
+ || StringUtils.isBlank(task.getHtmlBody()) || StringUtils.isBlank(task.getTextBody())) {
+
+ String message = "Could not fetch all required information for sending e-mails:\n"
+ + task.getRecipients() + "\n"
+ + task.getSender() + "\n"
+ + task.getSubject() + "\n"
+ + task.getHtmlBody() + "\n"
+ + task.getTextBody();
+ LOG.error(message);
+
+ execution.setStatus(NotificationJob.Status.NOT_SENT.name());
+ retryPossible = false;
+
+ if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+ execution.setMessage(message);
+ }
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("About to send e-mails:\n"
+ + task.getRecipients() + "\n"
+ + task.getSender() + "\n"
+ + task.getSubject() + "\n"
+ + task.getHtmlBody() + "\n"
+ + task.getTextBody() + "\n");
+ }
+
+ for (String to : task.getRecipients()) {
+ try {
+ MimeMessage message = mailSender.createMimeMessage();
+ MimeMessageHelper helper = new MimeMessageHelper(message, true);
+ helper.setTo(to);
+ helper.setFrom(task.getSender());
+ helper.setSubject(task.getSubject());
+ helper.setText(task.getTextBody(), task.getHtmlBody());
+
+ mailSender.send(message);
+
+ execution.setStatus(NotificationJob.Status.SENT.name());
+
+ StringBuilder report = new StringBuilder();
+ switch (task.getTraceLevel()) {
+ case ALL:
+ report.append("FROM: ").append(task.getSender()).append('\n').
+ append("TO: ").append(to).append('\n').
+ append("SUBJECT: ").append(task.getSubject()).append('\n').append('\n').
+ append(task.getTextBody()).append('\n').append('\n').
+ append(task.getHtmlBody()).append('\n');
+ break;
+
+ case SUMMARY:
+ report.append("E-mail sent to ").append(to).append('\n');
+ break;
+
+ case FAILURES:
+ case NONE:
+ default:
+ }
+ if (report.length() > 0) {
+ execution.setMessage(report.toString());
+ }
+
+ publisher.publishEvent(new AfterHandlingEvent(this,
+ true,
+ true,
+ AuditElements.EventCategoryType.TASK,
+ "notification",
+ null,
+ "send",
+ AuditElements.Result.SUCCESS,
+ null,
+ null,
+ task,
+ "Successfully sent notification to " + to));
+ } catch (Exception e) {
+ LOG.error("Could not send e-mail", e);
+
+ execution.setStatus(NotificationJob.Status.NOT_SENT.name());
+ if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) {
+ execution.setMessage(ExceptionUtils2.getFullStackTrace(e));
+ }
+
+ publisher.publishEvent(new AfterHandlingEvent(this,
+ true,
+ true,
+ AuditElements.EventCategoryType.TASK,
+ "notification",
+ null,
+ "send",
+ AuditElements.Result.FAILURE,
+ null,
+ null,
+ task,
+ "Could not send notification to " + to, e));
+ }
+
+ execution.setEnd(new Date());
+ }
+ }
+
+ if (hasToBeRegistered(execution)) {
+ execution = notificationManager.storeExec(execution);
+ if (retryPossible
+ && (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT)) {
+
+ handleRetries(execution);
+ }
+ } else {
+ notificationManager.setTaskExecuted(execution.getTask().getKey(), true);
+ }
+
+ return execution;
+ }
+
+ @Transactional
+ public void execute() throws JobExecutionException {
+ for (NotificationTask task : taskDAO.<NotificationTask>findToExec(TaskType.NOTIFICATION)) {
+ LOG.debug("Found notification task {} to be executed: starting...", task);
+ executeSingle(task);
+ LOG.debug("Notification task {} executed", task);
+ }
+ }
+
+ private boolean hasToBeRegistered(final TaskExec execution) {
+ NotificationTask task = (NotificationTask) execution.getTask();
+
+ // True if either failed and failures have to be registered, or if ALL
+ // has to be registered.
+ return (NotificationJob.Status.valueOf(execution.getStatus()) == NotificationJob.Status.NOT_SENT
+ && task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+ || task.getTraceLevel() == TraceLevel.ALL;
+ }
+
+ private void handleRetries(final TaskExec execution) {
+ if (maxRetries <= 0) {
+ return;
+ }
+
+ long failedExecutionsCount = notificationManager.countExecutionsWithStatus(
+ execution.getTask().getKey(), NotificationJob.Status.NOT_SENT.name());
+
+ if (failedExecutionsCount <= maxRetries) {
+ LOG.debug("Execution of notification task {} will be retried [{}/{}]",
+ execution.getTask(), failedExecutionsCount, maxRetries);
+ notificationManager.setTaskExecuted(execution.getTask().getKey(), false);
+
+ auditManager.audit(
+ AuditElements.EventCategoryType.TASK,
+ "notification",
+ null,
+ "retry",
+ AuditElements.Result.SUCCESS,
+ null,
+ null,
+ execution,
+ "Notification task " + execution.getTask().getKey() + " will be retried");
+ } else {
+ LOG.error("Maximum number of retries reached for task {} - giving up", execution.getTask());
+
+ auditManager.audit(
+ AuditElements.EventCategoryType.TASK,
+ "notification",
+ null,
+ "retry",
+ AuditElements.Result.FAILURE,
+ null,
+ null,
+ execution,
+ "Giving up retries on notification task " + execution.getTask().getKey());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
new file mode 100644
index 0000000..2c85b20
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AbstractReportlet.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.transaction.annotation.Transactional;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+public abstract class AbstractReportlet implements Reportlet {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(AbstractReportlet.class);
+
+ protected abstract void doExtract(ReportletConf conf, ContentHandler handler) throws SAXException;
+
+ @Override
+ @Transactional(readOnly = true)
+ public void extract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+ if (conf == null) {
+ throw new ReportException(new IllegalArgumentException("No configuration provided"));
+ }
+
+ AttributesImpl atts = new AttributesImpl();
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, conf.getName());
+ atts.addAttribute("", "", ReportXMLConst.ATTR_CLASS, ReportXMLConst.XSD_STRING, getClass().getName());
+ handler.startElement("", "", ReportXMLConst.ELEMENT_REPORTLET, atts);
+
+ doExtract(conf, handler);
+
+ handler.endElement("", "", ReportXMLConst.ELEMENT_REPORTLET);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
new file mode 100644
index 0000000..96039f0
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/AuditReportlet.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import java.util.List;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.syncope.common.lib.report.AuditReportletConf;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.core.provisioning.java.AuditEntry;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.persistence.api.DomainsHolder;
+import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+@ReportletConfClass(AuditReportletConf.class)
+public class AuditReportlet extends AbstractReportlet {
+
+ @Autowired
+ private DomainsHolder domainsHolder;
+
+ private AuditReportletConf conf;
+
+ private DataSource datasource;
+
+ private void doExtractConf(final ContentHandler handler) throws SAXException {
+ JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
+ jdbcTemplate.setMaxRows(conf.getSize());
+ List<Map<String, Object>> rows = jdbcTemplate.
+ queryForList("SELECT * FROM SYNCOPEAUDIT ORDER BY EVENT_DATE DESC");
+
+ handler.startElement("", "", "events", null);
+ AttributesImpl atts = new AttributesImpl();
+ for (Map<String, Object> row : rows) {
+ AuditEntry auditEntry = POJOHelper.deserialize(row.get("MESSAGE").toString(), AuditEntry.class);
+
+ atts.clear();
+ if (StringUtils.isNotBlank(auditEntry.getWho())) {
+ atts.addAttribute("", "", "who", ReportXMLConst.XSD_STRING, auditEntry.getWho());
+ }
+ handler.startElement("", "", "event", atts);
+
+ atts.clear();
+ if (StringUtils.isNotBlank(auditEntry.getLogger().getCategory())) {
+ atts.addAttribute("", "", "category",
+ ReportXMLConst.XSD_STRING, auditEntry.getLogger().getCategory());
+ }
+ if (StringUtils.isNotBlank(auditEntry.getLogger().getSubcategory())) {
+ atts.addAttribute("", "", "subcategory",
+ ReportXMLConst.XSD_STRING, auditEntry.getLogger().getSubcategory());
+ }
+ if (StringUtils.isNotBlank(auditEntry.getLogger().getEvent())) {
+ atts.addAttribute("", "", "event",
+ ReportXMLConst.XSD_STRING, auditEntry.getLogger().getEvent());
+ }
+ if (auditEntry.getLogger().getResult() != null) {
+ atts.addAttribute("", "", "result",
+ ReportXMLConst.XSD_STRING, auditEntry.getLogger().getResult().name());
+ }
+ handler.startElement("", "", "logger", atts);
+ handler.endElement("", "", "logger");
+
+ if (auditEntry.getBefore() != null) {
+ char[] before = ToStringBuilder.reflectionToString(
+ auditEntry.getBefore(), ToStringStyle.MULTI_LINE_STYLE).toCharArray();
+ handler.startElement("", "", "before", null);
+ handler.characters(before, 0, before.length);
+ handler.endElement("", "", "before");
+ }
+
+ if (auditEntry.getInput() != null) {
+ handler.startElement("", "", "inputs", null);
+ for (Object inputObj : auditEntry.getInput()) {
+ char[] input = ToStringBuilder.reflectionToString(
+ inputObj, ToStringStyle.MULTI_LINE_STYLE).toCharArray();
+ handler.startElement("", "", "input", null);
+ handler.characters(input, 0, input.length);
+ handler.endElement("", "", "input");
+ }
+ handler.endElement("", "", "inputs");
+ }
+
+ if (auditEntry.getOutput() != null) {
+ char[] output = ToStringBuilder.reflectionToString(
+ auditEntry.getOutput(), ToStringStyle.MULTI_LINE_STYLE).toCharArray();
+ handler.startElement("", "", "output", null);
+ handler.characters(output, 0, output.length);
+ handler.endElement("", "", "output");
+ }
+
+ handler.startElement("", "", "throwable", null);
+ char[] throwable = row.get("THROWABLE").toString().toCharArray();
+ handler.characters(throwable, 0, throwable.length);
+ handler.endElement("", "", "throwable");
+
+ handler.endElement("", "", "event");
+ }
+ handler.endElement("", "", "events");
+ }
+
+ @Override
+ protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+ if (conf instanceof AuditReportletConf) {
+ this.conf = AuditReportletConf.class.cast(conf);
+ } else {
+ throw new ReportException(new IllegalArgumentException("Invalid configuration provided"));
+ }
+
+ datasource = domainsHolder.getDomains().get(AuthContextUtils.getDomain());
+ if (datasource == null) {
+ throw new ReportException(new IllegalArgumentException("Could not get to DataSource"));
+ }
+
+ doExtractConf(handler);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
new file mode 100644
index 0000000..fa558b6
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.report.GroupReportletConf;
+import org.apache.syncope.common.lib.report.GroupReportletConf.Feature;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
+import org.apache.syncope.core.persistence.api.entity.user.UMembership;
+import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+@ReportletConfClass(GroupReportletConf.class)
+public class GroupReportlet extends AbstractReportlet {
+
+ @Autowired
+ private GroupDAO groupDAO;
+
+ @Autowired
+ private AnySearchDAO searchDAO;
+
+ @Autowired
+ private GroupDataBinder groupDataBinder;
+
+ private GroupReportletConf conf;
+
+ private void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
+ throws SAXException {
+
+ if (anyTO.getResources().isEmpty()) {
+ LOG.debug("No resources found for {}[{}]", anyTO.getClass().getSimpleName(), anyTO.getKey());
+ } else {
+ AttributesImpl atts = new AttributesImpl();
+ handler.startElement("", "", "resources", null);
+
+ for (String resourceName : anyTO.getResources()) {
+ atts.clear();
+
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, resourceName);
+ handler.startElement("", "", "resource", atts);
+ handler.endElement("", "", "resource");
+ }
+
+ handler.endElement("", "", "resources");
+ }
+ }
+
+ private void doExtractAttributes(final ContentHandler handler, final AnyTO anyTO,
+ final Collection<String> attrs, final Collection<String> derAttrs, final Collection<String> virAttrs)
+ throws SAXException {
+
+ AttributesImpl atts = new AttributesImpl();
+ if (!attrs.isEmpty()) {
+ Map<String, AttrTO> attrMap = anyTO.getPlainAttrMap();
+
+ handler.startElement("", "", "attributes", null);
+ for (String attrName : attrs) {
+ atts.clear();
+
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+ handler.startElement("", "", "attribute", atts);
+
+ if (attrMap.containsKey(attrName)) {
+ for (String value : attrMap.get(attrName).getValues()) {
+ handler.startElement("", "", "value", null);
+ handler.characters(value.toCharArray(), 0, value.length());
+ handler.endElement("", "", "value");
+ }
+ } else {
+ LOG.debug("{} not found for {}[{}]", attrName,
+ anyTO.getClass().getSimpleName(), anyTO.getKey());
+ }
+
+ handler.endElement("", "", "attribute");
+ }
+ handler.endElement("", "", "attributes");
+ }
+
+ if (!derAttrs.isEmpty()) {
+ Map<String, AttrTO> derAttrMap = anyTO.getDerAttrMap();
+
+ handler.startElement("", "", "derivedAttributes", null);
+ for (String attrName : derAttrs) {
+ atts.clear();
+
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+ handler.startElement("", "", "derivedAttribute", atts);
+
+ if (derAttrMap.containsKey(attrName)) {
+ for (String value : derAttrMap.get(attrName).getValues()) {
+ handler.startElement("", "", "value", null);
+ handler.characters(value.toCharArray(), 0, value.length());
+ handler.endElement("", "", "value");
+ }
+ } else {
+ LOG.debug("{} not found for {}[{}]", attrName,
+ anyTO.getClass().getSimpleName(), anyTO.getKey());
+ }
+
+ handler.endElement("", "", "derivedAttribute");
+ }
+ handler.endElement("", "", "derivedAttributes");
+ }
+
+ if (!virAttrs.isEmpty()) {
+ Map<String, AttrTO> virAttrMap = anyTO.getVirAttrMap();
+
+ handler.startElement("", "", "virtualAttributes", null);
+ for (String attrName : virAttrs) {
+ atts.clear();
+
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, attrName);
+ handler.startElement("", "", "virtualAttribute", atts);
+
+ if (virAttrMap.containsKey(attrName)) {
+ for (String value : virAttrMap.get(attrName).getValues()) {
+ handler.startElement("", "", "value", null);
+ handler.characters(value.toCharArray(), 0, value.length());
+ handler.endElement("", "", "value");
+ }
+ } else {
+ LOG.debug("{} not found for {}[{}]", attrName,
+ anyTO.getClass().getSimpleName(), anyTO.getKey());
+ }
+
+ handler.endElement("", "", "virtualAttribute");
+ }
+ handler.endElement("", "", "virtualAttributes");
+ }
+ }
+
+ private void doExtract(final ContentHandler handler, final List<Group> groups) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ for (Group group : groups) {
+ atts.clear();
+
+ for (Feature feature : conf.getFeatures()) {
+ String type = null;
+ String value = null;
+ switch (feature) {
+ case key:
+ type = ReportXMLConst.XSD_STRING;
+ value = group.getKey();
+ break;
+
+ case name:
+ type = ReportXMLConst.XSD_STRING;
+ value = String.valueOf(group.getName());
+ break;
+
+ case groupOwner:
+ type = ReportXMLConst.XSD_STRING;
+ value = group.getGroupOwner().getKey();
+ break;
+
+ case userOwner:
+ type = ReportXMLConst.XSD_STRING;
+ value = group.getUserOwner().getKey();
+ break;
+
+ default:
+ }
+
+ if (type != null && value != null) {
+ atts.addAttribute("", "", feature.name(), type, value);
+ }
+ }
+
+ handler.startElement("", "", "group", atts);
+
+ // Using GroupTO for attribute values, since the conversion logic of
+ // values to String is already encapsulated there
+ GroupTO groupTO = groupDataBinder.getGroupTO(group, true);
+
+ doExtractAttributes(handler, groupTO, conf.getPlainAttrs(), conf.getDerAttrs(), conf.getVirAttrs());
+
+ // to get resources associated to a group
+ if (conf.getFeatures().contains(Feature.resources)) {
+ doExtractResources(handler, groupTO);
+ }
+ //to get users asscoiated to a group is preferred GroupDAO to GroupTO
+ if (conf.getFeatures().contains(Feature.users)) {
+ handler.startElement("", "", "users", null);
+
+ for (UMembership memb : groupDAO.findUMemberships(group)) {
+ atts.clear();
+
+ atts.addAttribute("", "", "key", ReportXMLConst.XSD_STRING,
+ memb.getLeftEnd().getKey());
+ atts.addAttribute("", "", "username", ReportXMLConst.XSD_STRING,
+ String.valueOf(memb.getLeftEnd().getUsername()));
+
+ handler.startElement("", "", "user", atts);
+ handler.endElement("", "", "user");
+ }
+
+ handler.endElement("", "", "users");
+ }
+
+ handler.endElement("", "", "group");
+ }
+ }
+
+ private void doExtractConf(final ContentHandler handler) throws SAXException {
+ if (conf == null) {
+ LOG.debug("Report configuration is not present");
+ }
+
+ AttributesImpl atts = new AttributesImpl();
+ handler.startElement("", "", "configurations", null);
+ handler.startElement("", "", "groupAttributes", atts);
+
+ if (conf != null) {
+ for (Feature feature : conf.getFeatures()) {
+ atts.clear();
+ handler.startElement("", "", "feature", atts);
+ handler.characters(feature.name().toCharArray(), 0, feature.name().length());
+ handler.endElement("", "", "feature");
+ }
+
+ for (String attr : conf.getPlainAttrs()) {
+ atts.clear();
+ handler.startElement("", "", "attribute", atts);
+ handler.characters(attr.toCharArray(), 0, attr.length());
+ handler.endElement("", "", "attribute");
+ }
+
+ for (String derAttr : conf.getDerAttrs()) {
+ atts.clear();
+ handler.startElement("", "", "derAttribute", atts);
+ handler.characters(derAttr.toCharArray(), 0, derAttr.length());
+ handler.endElement("", "", "derAttribute");
+ }
+
+ for (String virAttr : conf.getVirAttrs()) {
+ atts.clear();
+ handler.startElement("", "", "virAttribute", atts);
+ handler.characters(virAttr.toCharArray(), 0, virAttr.length());
+ handler.endElement("", "", "virAttribute");
+ }
+ }
+
+ handler.endElement("", "", "groupAttributes");
+ handler.endElement("", "", "configurations");
+ }
+
+ private int count() {
+ return StringUtils.isBlank(conf.getMatchingCond())
+ ? groupDAO.count()
+ : searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS,
+ SearchCondConverter.convert(conf.getMatchingCond()), AnyTypeKind.GROUP);
+ }
+
+ @Override
+ protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+ if (conf instanceof GroupReportletConf) {
+ this.conf = GroupReportletConf.class.cast(conf);
+ } else {
+ throw new ReportException(new IllegalArgumentException("Invalid configuration provided"));
+ }
+
+ doExtractConf(handler);
+
+ for (int page = 1; page <= (count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+ List<Group> groups;
+ if (StringUtils.isBlank(this.conf.getMatchingCond())) {
+ groups = groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE);
+ } else {
+ groups = searchDAO.search(
+ SyncopeConstants.FULL_ADMIN_REALMS,
+ SearchCondConverter.convert(this.conf.getMatchingCond()),
+ page,
+ AnyDAO.DEFAULT_PAGE_SIZE,
+ Collections.<OrderByClause>emptyList(),
+ AnyTypeKind.USER);
+ }
+
+ doExtract(handler, groups);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
new file mode 100644
index 0000000..f39ee1c
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
@@ -0,0 +1,519 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.commons.collections4.Closure;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.report.ReconciliationReportletConf;
+import org.apache.syncope.common.lib.report.ReconciliationReportletConf.Feature;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
+import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
+import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.MappingManager;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.common.Base64;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Reportlet for extracting information for the current reconciliation status, e.g. the coherence between Syncope
+ * information and mapped entities on external resources.
+ */
+@ReportletConfClass(ReconciliationReportletConf.class)
+public class ReconciliationReportlet extends AbstractReportlet {
+
+ private static final int PAGE_SIZE = 10;
+
+ @Autowired
+ private UserDAO userDAO;
+
+ @Autowired
+ private GroupDAO groupDAO;
+
+ @Autowired
+ private AnyTypeDAO anyTypeDAO;
+
+ @Autowired
+ private AnySearchDAO searchDAO;
+
+ @Autowired
+ private MappingManager mappingManager;
+
+ @Autowired
+ private ConnectorFactory connFactory;
+
+ @Autowired
+ private AnyUtilsFactory anyUtilsFactory;
+
+ private ReconciliationReportletConf conf;
+
+ private String getAnyElementName(final AnyTypeKind anyTypeKind) {
+ String elementName;
+
+ switch (anyTypeKind) {
+ case USER:
+ elementName = "user";
+ break;
+
+ case GROUP:
+ elementName = "group";
+ break;
+
+ case ANY_OBJECT:
+ default:
+ elementName = "anyObject";
+ }
+
+ return elementName;
+ }
+
+ private void doExtract(
+ final ContentHandler handler,
+ final Any<?> any,
+ final Set<Missing> missing,
+ final Set<Misaligned> misaligned)
+ throws SAXException {
+
+ AttributesImpl atts = new AttributesImpl();
+
+ for (Feature feature : conf.getFeatures()) {
+ String type = null;
+ String value = null;
+ switch (feature) {
+ case key:
+ type = ReportXMLConst.XSD_STRING;
+ value = any.getKey();
+ break;
+
+ case username:
+ if (any instanceof User) {
+ type = ReportXMLConst.XSD_STRING;
+ value = User.class.cast(any).getUsername();
+ }
+ break;
+
+ case groupName:
+ if (any instanceof Group) {
+ type = ReportXMLConst.XSD_STRING;
+ value = Group.class.cast(any).getName();
+ }
+ break;
+
+ case workflowId:
+ type = ReportXMLConst.XSD_STRING;
+ value = any.getWorkflowId();
+ break;
+
+ case status:
+ type = ReportXMLConst.XSD_STRING;
+ value = any.getStatus();
+ break;
+
+ case creationDate:
+ type = ReportXMLConst.XSD_DATETIME;
+ value = any.getCreationDate() == null
+ ? StringUtils.EMPTY
+ : FormatUtils.format(any.getCreationDate());
+ break;
+
+ case lastLoginDate:
+ if (any instanceof User) {
+ type = ReportXMLConst.XSD_DATETIME;
+ value = User.class.cast(any).getLastLoginDate() == null
+ ? StringUtils.EMPTY
+ : FormatUtils.format(User.class.cast(any).getLastLoginDate());
+ }
+ break;
+
+ case changePwdDate:
+ if (any instanceof User) {
+ type = ReportXMLConst.XSD_DATETIME;
+ value = User.class.cast(any).getChangePwdDate() == null
+ ? StringUtils.EMPTY
+ : FormatUtils.format(User.class.cast(any).getChangePwdDate());
+ }
+ break;
+
+ case passwordHistorySize:
+ if (any instanceof User) {
+ type = ReportXMLConst.XSD_INT;
+ value = String.valueOf(User.class.cast(any).getPasswordHistory().size());
+ }
+ break;
+
+ case failedLoginCount:
+ if (any instanceof User) {
+ type = ReportXMLConst.XSD_INT;
+ value = String.valueOf(User.class.cast(any).getFailedLogins());
+ }
+ break;
+
+ default:
+ }
+
+ if (type != null && value != null) {
+ atts.addAttribute("", "", feature.name(), type, value);
+ }
+ }
+
+ handler.startElement("", "", getAnyElementName(any.getType().getKind()), atts);
+
+ for (Missing item : missing) {
+ atts.clear();
+ atts.addAttribute("", "", "resource", ReportXMLConst.XSD_STRING, item.getResource());
+ atts.addAttribute("", "", "connObjectKeyValue", ReportXMLConst.XSD_STRING, item.getConnObjectKeyValue());
+
+ handler.startElement("", "", "missing", atts);
+ handler.endElement("", "", "missing");
+ }
+ for (Misaligned item : misaligned) {
+ atts.clear();
+ atts.addAttribute("", "", "resource", ReportXMLConst.XSD_STRING, item.getResource());
+ atts.addAttribute("", "", "connObjectKeyValue", ReportXMLConst.XSD_STRING, item.getConnObjectKeyValue());
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, item.getName());
+
+ handler.startElement("", "", "misaligned", atts);
+
+ handler.startElement("", "", "onSyncope", null);
+ if (item.getOnSyncope() != null) {
+ for (Object value : item.getOnSyncope()) {
+ char[] asChars = value.toString().toCharArray();
+
+ handler.startElement("", "", "value", null);
+ handler.characters(asChars, 0, asChars.length);
+ handler.endElement("", "", "value");
+ }
+ }
+ handler.endElement("", "", "onSyncope");
+
+ handler.startElement("", "", "onResource", null);
+ if (item.getOnResource() != null) {
+ for (Object value : item.getOnResource()) {
+ char[] asChars = value.toString().toCharArray();
+
+ handler.startElement("", "", "value", null);
+ handler.characters(asChars, 0, asChars.length);
+ handler.endElement("", "", "value");
+ }
+ }
+ handler.endElement("", "", "onResource");
+
+ handler.endElement("", "", "misaligned");
+ }
+
+ handler.endElement("", "", getAnyElementName(any.getType().getKind()));
+ }
+
+ private Set<Object> getValues(final Attribute attr) {
+ Set<Object> values;
+ if (attr.getValue() == null || attr.getValue().isEmpty()) {
+ values = Collections.emptySet();
+ } else if (attr.getValue().get(0) instanceof byte[]) {
+ values = new HashSet<>(attr.getValue().size());
+ for (Object single : attr.getValue()) {
+ values.add(Base64.encode((byte[]) single));
+ }
+ } else {
+ values = new HashSet<>(attr.getValue());
+ }
+
+ return values;
+ }
+
+ private void doExtract(final ContentHandler handler, final List<? extends Any<?>> anys)
+ throws SAXException, ReportException {
+
+ final Set<Missing> missing = new HashSet<>();
+ final Set<Misaligned> misaligned = new HashSet<>();
+
+ for (Any<?> any : anys) {
+ missing.clear();
+ misaligned.clear();
+
+ AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
+ for (final ExternalResource resource : anyUtils.getAllResources(any)) {
+ Provision provision = resource.getProvision(any.getType());
+ MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
+ final String connObjectKeyValue = connObjectKeyItem == null
+ ? StringUtils.EMPTY
+ : mappingManager.getConnObjectKeyValue(any, provision);
+ if (provision != null && connObjectKeyItem != null && StringUtils.isNotBlank(connObjectKeyValue)) {
+ // 1. read from the underlying connector
+ Connector connector = connFactory.getConnector(resource);
+ ConnectorObject connectorObject = connector.getObject(provision.getObjectClass(),
+ new Uid(connObjectKeyValue),
+ MappingUtils.buildOperationOptions(provision.getMapping().getItems().iterator()));
+
+ if (connectorObject == null) {
+ // 2. not found on resource?
+ LOG.error("Object {} with class {} not found on resource {}",
+ connObjectKeyValue, provision.getObjectClass(), resource);
+
+ missing.add(new Missing(resource.getKey(), connObjectKeyValue));
+ } else {
+ // 3. found but misaligned?
+ Pair<String, Set<Attribute>> preparedAttrs =
+ mappingManager.prepareAttrs(any, null, false, null, provision);
+ preparedAttrs.getRight().add(AttributeBuilder.build(
+ Uid.NAME, preparedAttrs.getLeft()));
+ preparedAttrs.getRight().add(AttributeBuilder.build(
+ connObjectKeyItem.getExtAttrName(), preparedAttrs.getLeft()));
+
+ final Map<String, Set<Object>> syncopeAttrs = new HashMap<>();
+ for (Attribute attr : preparedAttrs.getRight()) {
+ syncopeAttrs.put(attr.getName(), getValues(attr));
+ }
+
+ final Map<String, Set<Object>> resourceAttrs = new HashMap<>();
+ for (Attribute attr : connectorObject.getAttributes()) {
+ if (!OperationalAttributes.PASSWORD_NAME.equals(attr.getName())
+ && !OperationalAttributes.ENABLE_NAME.equals(attr.getName())) {
+
+ resourceAttrs.put(attr.getName(), getValues(attr));
+ }
+ }
+
+ IterableUtils.forEach(CollectionUtils.subtract(syncopeAttrs.keySet(), resourceAttrs.keySet()),
+ new Closure<String>() {
+
+ @Override
+ public void execute(final String name) {
+ misaligned.add(new Misaligned(
+ resource.getKey(),
+ connObjectKeyValue,
+ name,
+ syncopeAttrs.get(name),
+ Collections.emptySet()));
+ }
+ });
+
+ for (Map.Entry<String, Set<Object>> entry : resourceAttrs.entrySet()) {
+ if (syncopeAttrs.containsKey(entry.getKey())) {
+ if (!Objects.equals(syncopeAttrs.get(entry.getKey()), entry.getValue())) {
+ misaligned.add(new Misaligned(
+ resource.getKey(),
+ connObjectKeyValue,
+ entry.getKey(),
+ syncopeAttrs.get(entry.getKey()),
+ entry.getValue()));
+ }
+ } else {
+ misaligned.add(new Misaligned(
+ resource.getKey(),
+ connObjectKeyValue,
+ entry.getKey(),
+ Collections.emptySet(),
+ entry.getValue()));
+ }
+ }
+ }
+ }
+ }
+
+ if (!missing.isEmpty() || !misaligned.isEmpty()) {
+ doExtract(handler, any, missing, misaligned);
+ }
+ }
+ }
+
+ private void doExtract(
+ final ContentHandler handler, final int count, final SearchCond cond, final AnyTypeKind anyTypeKind)
+ throws SAXException {
+
+ for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
+ List<AnyObject> anys = searchDAO.search(
+ SyncopeConstants.FULL_ADMIN_REALMS,
+ cond,
+ page,
+ PAGE_SIZE,
+ Collections.<OrderByClause>emptyList(),
+ anyTypeKind);
+
+ doExtract(handler, anys);
+ }
+ }
+
+ @Override
+ protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+ if (conf instanceof ReconciliationReportletConf) {
+ this.conf = ReconciliationReportletConf.class.cast(conf);
+ } else {
+ throw new ReportException(new IllegalArgumentException("Invalid configuration provided"));
+ }
+
+ AttributesImpl atts = new AttributesImpl();
+
+ if (StringUtils.isBlank(this.conf.getUserMatchingCond())) {
+ atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(userDAO.count()));
+ handler.startElement("", "", getAnyElementName(AnyTypeKind.USER) + "s", atts);
+
+ for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+ doExtract(handler, userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
+ }
+ } else {
+ SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
+
+ int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
+ atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+ handler.startElement("", "", getAnyElementName(AnyTypeKind.USER) + "s", atts);
+
+ doExtract(handler, count, cond, AnyTypeKind.USER);
+ }
+ handler.endElement("", "", getAnyElementName(AnyTypeKind.USER) + "s");
+
+ atts.clear();
+ if (StringUtils.isBlank(this.conf.getGroupMatchingCond())) {
+ atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(groupDAO.count()));
+ handler.startElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s", atts);
+
+ for (int page = 1; page <= (groupDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
+ doExtract(handler, groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
+ }
+ } else {
+ SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
+
+ int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
+ atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+ handler.startElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s", atts);
+
+ doExtract(handler, count, cond, AnyTypeKind.GROUP);
+ }
+ handler.endElement("", "", getAnyElementName(AnyTypeKind.GROUP) + "s");
+
+ for (AnyType anyType : anyTypeDAO.findAll()) {
+ if (!anyType.equals(anyTypeDAO.findUser()) && !anyType.equals(anyTypeDAO.findGroup())) {
+ AnyTypeCond anyTypeCond = new AnyTypeCond();
+ anyTypeCond.setAnyTypeKey(anyType.getKey());
+ SearchCond cond = StringUtils.isBlank(this.conf.getAnyObjectMatchingCond())
+ ? SearchCond.getLeafCond(anyTypeCond)
+ : SearchCond.getAndCond(
+ SearchCond.getLeafCond(anyTypeCond),
+ SearchCondConverter.convert(this.conf.getAnyObjectMatchingCond()));
+
+ int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
+
+ atts.clear();
+ atts.addAttribute("", "", "type", ReportXMLConst.XSD_STRING, anyType.getKey());
+ atts.addAttribute("", "", "total", ReportXMLConst.XSD_INT, String.valueOf(count));
+ handler.startElement("", "", getAnyElementName(AnyTypeKind.ANY_OBJECT) + "s", atts);
+
+ doExtract(handler, count, cond, AnyTypeKind.ANY_OBJECT);
+
+ handler.endElement("", "", getAnyElementName(AnyTypeKind.ANY_OBJECT) + "s");
+ }
+ }
+ }
+
+ private static class Missing {
+
+ private final String resource;
+
+ private final String connObjectKeyValue;
+
+ Missing(final String resource, final String connObjectKeyValue) {
+ this.resource = resource;
+ this.connObjectKeyValue = connObjectKeyValue;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+
+ public String getConnObjectKeyValue() {
+ return connObjectKeyValue;
+ }
+
+ }
+
+ private static class Misaligned extends Missing {
+
+ private final String name;
+
+ private final Set<Object> onSyncope;
+
+ private final Set<Object> onResource;
+
+ Misaligned(
+ final String resource,
+ final String connObjectKeyValue,
+ final String name,
+ final Set<Object> onSyncope,
+ final Set<Object> onResource) {
+
+ super(resource, connObjectKeyValue);
+
+ this.name = name;
+ this.onSyncope = onSyncope;
+ this.onResource = onResource;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set<Object> getOnSyncope() {
+ return onSyncope;
+ }
+
+ public Set<Object> getOnResource() {
+ return onResource;
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportException.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportException.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportException.java
new file mode 100644
index 0000000..db42152
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+public class ReportException extends RuntimeException {
+
+ private static final long serialVersionUID = 6719507778589395283L;
+
+ public ReportException(final Throwable cause) {
+ super(cause);
+ }
+
+ public ReportException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
new file mode 100644
index 0000000..78183d5
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJob.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.provisioning.java.job.AbstractInterruptableJob;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Quartz job for executing a given report.
+ */
+public class ReportJob extends AbstractInterruptableJob {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ReportJob.class);
+
+ /**
+ * Key, set by the caller, for identifying the report to be executed.
+ */
+ private String reportKey;
+
+ @Autowired
+ private ReportJobDelegate delegate;
+
+ /**
+ * Report id setter.
+ *
+ * @param reportKey to be set
+ */
+ public void setReportKey(final String reportKey) {
+ this.reportKey = reportKey;
+ }
+
+ @Override
+ public void execute(final JobExecutionContext context) throws JobExecutionException {
+ super.execute(context);
+
+ try {
+ AuthContextUtils.execWithAuthContext(context.getMergedJobDataMap().getString(JobManager.DOMAIN_KEY),
+ new AuthContextUtils.Executable<Void>() {
+
+ @Override
+ public Void exec() {
+ try {
+ delegate.execute(reportKey);
+ } catch (Exception e) {
+ LOG.error("While executing report {}", reportKey, e);
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+ });
+ } catch (RuntimeException e) {
+ LOG.error("While executing report {}", reportKey, e);
+ throw new JobExecutionException("While executing report " + reportKey, e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
new file mode 100644
index 0000000..28f4894
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportJobDelegate.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.apache.commons.io.IOUtils;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.types.ReportExecStatus;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.dao.ReportDAO;
+import org.apache.syncope.core.persistence.api.dao.ReportExecDAO;
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Report;
+import org.apache.syncope.core.persistence.api.entity.ReportExec;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.xml.sax.helpers.AttributesImpl;
+
+@Component
+public class ReportJobDelegate {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ReportJobDelegate.class);
+
+ /**
+ * Report DAO.
+ */
+ @Autowired
+ private ReportDAO reportDAO;
+
+ /**
+ * Report execution DAO.
+ */
+ @Autowired
+ private ReportExecDAO reportExecDAO;
+
+ @Autowired
+ private EntityFactory entityFactory;
+
+ @Autowired
+ private ImplementationLookup implementationLookup;
+
+ @Transactional
+ public void execute(final String reportKey) throws JobExecutionException {
+ Report report = reportDAO.find(reportKey);
+ if (report == null) {
+ throw new JobExecutionException("Report " + reportKey + " not found");
+ }
+
+ if (!report.isActive()) {
+ LOG.info("Report {} not active, aborting...", reportKey);
+ return;
+ }
+
+ // 1. create execution
+ ReportExec execution = entityFactory.newEntity(ReportExec.class);
+ execution.setStatus(ReportExecStatus.STARTED);
+ execution.setStart(new Date());
+ execution.setReport(report);
+ execution = reportExecDAO.save(execution);
+
+ report.add(execution);
+ report = reportDAO.save(report);
+
+ // 2. define a SAX handler for generating result as XML
+ TransformerHandler handler;
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ZipOutputStream zos = new ZipOutputStream(baos);
+ zos.setLevel(Deflater.BEST_COMPRESSION);
+ try {
+ SAXTransformerFactory tFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
+ tFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ handler = tFactory.newTransformerHandler();
+ Transformer serializer = handler.getTransformer();
+ serializer.setOutputProperty(OutputKeys.ENCODING, StandardCharsets.UTF_8.name());
+ serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+ // a single ZipEntry in the ZipOutputStream
+ zos.putNextEntry(new ZipEntry(report.getName()));
+
+ // streaming SAX handler in a compressed byte array stream
+ handler.setResult(new StreamResult(zos));
+ } catch (Exception e) {
+ throw new JobExecutionException("While configuring for SAX generation", e, true);
+ }
+
+ execution.setStatus(ReportExecStatus.RUNNING);
+ execution = reportExecDAO.save(execution);
+
+ // 3. actual report execution
+ StringBuilder reportExecutionMessage = new StringBuilder();
+ try {
+ // report header
+ handler.startDocument();
+ AttributesImpl atts = new AttributesImpl();
+ atts.addAttribute("", "", ReportXMLConst.ATTR_NAME, ReportXMLConst.XSD_STRING, report.getName());
+ handler.startElement("", "", ReportXMLConst.ELEMENT_REPORT, atts);
+
+ // iterate over reportlet instances defined for this report
+ for (ReportletConf reportletConf : report.getReportletConfs()) {
+ Class<? extends Reportlet> reportletClass =
+ implementationLookup.getReportletClass(reportletConf.getClass());
+ if (reportletClass == null) {
+ LOG.warn("Could not find matching reportlet for {}", reportletConf.getClass());
+ } else {
+ // fetch (or create) reportlet
+ Reportlet reportlet;
+ if (ApplicationContextProvider.getBeanFactory().containsSingleton(reportletClass.getName())) {
+ reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
+ getSingleton(reportletClass.getName());
+ } else {
+ reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
+ createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+ ApplicationContextProvider.getBeanFactory().
+ registerSingleton(reportletClass.getName(), reportlet);
+ }
+
+ // invoke reportlet
+ try {
+ reportlet.extract(reportletConf, handler);
+ } catch (Throwable t) {
+ LOG.error("While executing reportlet {} for report {}", reportlet, reportKey, t);
+
+ execution.setStatus(ReportExecStatus.FAILURE);
+
+ Throwable effective = t instanceof ReportException
+ ? t.getCause()
+ : t;
+ reportExecutionMessage.
+ append(ExceptionUtils2.getFullStackTrace(effective)).
+ append("\n==================\n");
+ }
+ }
+ }
+
+ // report footer
+ handler.endElement("", "", ReportXMLConst.ELEMENT_REPORT);
+ handler.endDocument();
+
+ if (!ReportExecStatus.FAILURE.name().equals(execution.getStatus())) {
+ execution.setStatus(ReportExecStatus.SUCCESS);
+ }
+ } catch (Exception e) {
+ execution.setStatus(ReportExecStatus.FAILURE);
+ reportExecutionMessage.append(ExceptionUtils2.getFullStackTrace(e));
+
+ throw new JobExecutionException(e, true);
+ } finally {
+ try {
+ zos.closeEntry();
+ IOUtils.closeQuietly(zos);
+ IOUtils.closeQuietly(baos);
+ } catch (IOException e) {
+ LOG.error("While closing StreamResult's backend", e);
+ }
+
+ execution.setExecResult(baos.toByteArray());
+ execution.setMessage(reportExecutionMessage.toString());
+ execution.setEnd(new Date());
+ reportExecDAO.save(execution);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportXMLConst.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportXMLConst.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportXMLConst.java
new file mode 100644
index 0000000..6b21695
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReportXMLConst.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+public final class ReportXMLConst {
+
+ public static final String XSD_STRING = "xsd:string";
+
+ public static final String XSD_INT = "xsd:integer";
+
+ public static final String XSD_LONG = "xsd:long";
+
+ public static final String XSD_BOOLEAN = "xsd:boolean";
+
+ public static final String XSD_DATETIME = "xsd:dateTime";
+
+ public static final String ELEMENT_REPORT = "report";
+
+ public static final String ATTR_NAME = "name";
+
+ public static final String ATTR_CLASS = "class";
+
+ public static final String ELEMENT_REPORTLET = "reportlet";
+
+ private ReportXMLConst() {
+ // empty private constructor for static utility class
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/67ecbea3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
new file mode 100644
index 0000000..1156d25
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/StaticReportlet.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job.report;
+
+import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.common.lib.report.StaticReportletConf;
+import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
+import org.springframework.util.StringUtils;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+@ReportletConfClass(StaticReportletConf.class)
+public class StaticReportlet extends AbstractReportlet {
+
+ private StaticReportletConf conf;
+
+ private void doExtractConf(final ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ handler.startElement("", "", "configurations", null);
+ handler.startElement("", "", "staticAttributes", atts);
+
+ handler.startElement("", "", "string", atts);
+ handler.characters("string".toCharArray(), 0, "string".length());
+ handler.endElement("", "", "string");
+
+ handler.startElement("", "", "long", atts);
+ handler.characters("long".toCharArray(), 0, "long".length());
+ handler.endElement("", "", "long");
+
+ handler.startElement("", "", "double", atts);
+ handler.characters("double".toCharArray(), 0, "double".length());
+ handler.endElement("", "", "double");
+
+ handler.startElement("", "", "date", atts);
+ handler.characters("date".toCharArray(), 0, "date".length());
+ handler.endElement("", "", "date");
+
+ handler.startElement("", "", "double", atts);
+ handler.characters("double".toCharArray(), 0, "double".length());
+ handler.endElement("", "", "double");
+
+ handler.startElement("", "", "enum", atts);
+ handler.characters("enum".toCharArray(), 0, "enum".length());
+ handler.endElement("", "", "enum");
+
+ handler.startElement("", "", "list", atts);
+ handler.characters("list".toCharArray(), 0, "list".length());
+ handler.endElement("", "", "list");
+
+ handler.endElement("", "", "staticAttributes");
+ handler.endElement("", "", "configurations");
+ }
+
+ @Override
+ protected void doExtract(final ReportletConf conf, final ContentHandler handler) throws SAXException {
+ if (conf instanceof StaticReportletConf) {
+ this.conf = StaticReportletConf.class.cast(conf);
+ } else {
+ throw new ReportException(new IllegalArgumentException("Invalid configuration provided"));
+ }
+
+ doExtractConf(handler);
+
+ if (StringUtils.hasText(this.conf.getStringField())) {
+ handler.startElement("", "", "string", null);
+ handler.characters(this.conf.getStringField().toCharArray(), 0, this.conf.getStringField().length());
+ handler.endElement("", "", "string");
+ }
+
+ if (this.conf.getLongField() != null) {
+ handler.startElement("", "", "long", null);
+ String printed = String.valueOf(this.conf.getLongField());
+ handler.characters(printed.toCharArray(), 0, printed.length());
+ handler.endElement("", "", "long");
+ }
+
+ if (this.conf.getDoubleField() != null) {
+ handler.startElement("", "", "double", null);
+ String printed = String.valueOf(this.conf.getDoubleField());
+ handler.characters(printed.toCharArray(), 0, printed.length());
+ handler.endElement("", "", "double");
+ }
+
+ if (this.conf.getDateField() != null) {
+ handler.startElement("", "", "date", null);
+ String printed = FormatUtils.format(this.conf.getDateField());
+ handler.characters(printed.toCharArray(), 0, printed.length());
+ handler.endElement("", "", "date");
+ }
+
+ if (this.conf.getTraceLevel() != null) {
+ handler.startElement("", "", "enum", null);
+ String printed = this.conf.getTraceLevel().name();
+ handler.characters(printed.toCharArray(), 0, printed.length());
+ handler.endElement("", "", "enum");
+ }
+
+ if (this.conf.getListField() != null && !this.conf.getListField().isEmpty()) {
+ handler.startElement("", "", "list", null);
+ for (String item : this.conf.getListField()) {
+ if (StringUtils.hasText(item)) {
+ handler.startElement("", "", "string", null);
+ handler.characters(item.toCharArray(), 0, item.length());
+ handler.endElement("", "", "string");
+ }
+ }
+ handler.endElement("", "", "list");
+ }
+ }
+}