http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java new file mode 100644 index 0000000..88d42fd --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/notification/NotificationJob.java @@ -0,0 +1,283 @@ +/* + * 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.logic.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.AuditElements.Result; +import org.apache.syncope.common.lib.types.TaskType; +import org.apache.syncope.common.lib.types.TraceLevel; +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.misc.AuditManager; +import org.apache.syncope.core.misc.ExceptionUtil; +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +/** + * Periodically checks for notification to send. + * + * @see NotificationTask + */ +@Component +@DisallowConcurrentExecution +public class NotificationJob implements Job { + + public enum Status { + + SENT, + NOT_SENT + + } + + public static final String DEFAULT_CRON_EXP = "0 0/5 * * * ?"; + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(NotificationJob.class); + + @Autowired + private AuditManager auditManager; + + @Autowired + private NotificationManager notificationManager; + + @Autowired + private JavaMailSender mailSender; + + @Autowired + private EntityFactory entityFactory; + + /** + * Task DAO. + */ + @Autowired + private TaskDAO taskDAO; + + 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); + } + } + + public TaskExec executeSingle(final NotificationTask task) { + init(); + + TaskExec execution = entityFactory.newEntity(TaskExec.class); + execution.setTask(task); + execution.setStartDate(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(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(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()); + } + + auditManager.audit( + AuditElements.EventCategoryType.TASK, + "notification", + null, + "send", + Result.SUCCESS, + null, + null, + task, + "Successfully sent notification to " + to); + } catch (Exception e) { + LOG.error("Could not send e-mail", e); + + execution.setStatus(Status.NOT_SENT.name()); + if (task.getTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) { + execution.setMessage(ExceptionUtil.getFullStackTrace(e)); + } + + auditManager.audit( + AuditElements.EventCategoryType.TASK, + "notification", + null, + "send", + Result.FAILURE, + null, + null, + task, + "Could not send notification to " + to, e); + } + + execution.setEndDate(new Date()); + } + } + + if (hasToBeRegistered(execution)) { + execution = notificationManager.storeExec(execution); + if (retryPossible && (Status.valueOf(execution.getStatus()) == Status.NOT_SENT)) { + handleRetries(execution); + } + } else { + notificationManager.setTaskExecuted(execution.getTask().getKey(), true); + } + + return execution; + } + + @Override + public void execute(final JobExecutionContext context) + throws JobExecutionException { + + LOG.debug("Waking up..."); + + 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); + } + + LOG.debug("Sleeping again..."); + } + + 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 (Status.valueOf(execution.getStatus()) == 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(), 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", + 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", + Result.FAILURE, + null, + null, + execution, + "Giving up retries on notification task " + execution.getTask().getKey()); + } + } +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/AbstractReportlet.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/AbstractReportlet.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/AbstractReportlet.java new file mode 100644 index 0000000..df32041 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/AbstractReportlet.java @@ -0,0 +1,66 @@ +/* + * 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.logic.report; + +import org.apache.syncope.common.lib.report.AbstractReportletConf; +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<T extends AbstractReportletConf> implements Reportlet<T> { + + /** + * Logger. + */ + protected static final Logger LOG = LoggerFactory.getLogger(AbstractReportlet.class); + + protected T conf; + + public T getConf() { + return conf; + } + + @Override + public void setConf(final T conf) { + this.conf = conf; + } + + protected abstract void doExtract(ContentHandler handler) throws SAXException, ReportException; + + @Override + @Transactional(readOnly = true) + public void extract(final ContentHandler handler) throws SAXException, ReportException { + + 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(handler); + + handler.endElement("", "", ReportXMLConst.ELEMENT_REPORTLET); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportException.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportException.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportException.java new file mode 100644 index 0000000..f4495ad --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/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.logic.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/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java new file mode 100644 index 0000000..7d03b61 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportJob.java @@ -0,0 +1,203 @@ +/* + * 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.logic.report; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +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.SyncopeConstants; +import org.apache.syncope.common.lib.report.ReportletConf; +import org.apache.syncope.common.lib.types.ReportExecStatus; +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.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.Report; +import org.apache.syncope.core.persistence.api.entity.ReportExec; +import org.apache.syncope.core.logic.ReportLogic; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.misc.ExceptionUtil; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +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.xml.sax.helpers.AttributesImpl; + +/** + * Quartz job for executing a given report. + */ +@SuppressWarnings("unchecked") +@DisallowConcurrentExecution +public class ReportJob implements Job { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(ReportJob.class); + + /** + * Report DAO. + */ + @Autowired + private ReportDAO reportDAO; + + /** + * Report execution DAO. + */ + @Autowired + private ReportExecDAO reportExecDAO; + + @Autowired + private ReportLogic dataBinder; + + @Autowired + private EntityFactory entityFactory; + + /** + * Key, set by the caller, for identifying the report to be executed. + */ + private Long reportKey; + + /** + * Report id setter. + * + * @param reportKey to be set + */ + public void setReportKey(final Long reportKey) { + this.reportKey = reportKey; + } + + @SuppressWarnings("rawtypes") + @Override + public void execute(final JobExecutionContext context) throws JobExecutionException { + Report report = reportDAO.find(reportKey); + if (report == null) { + throw new JobExecutionException("Report " + reportKey + " not found"); + } + + // 1. create execution + ReportExec execution = entityFactory.newEntity(ReportExec.class); + execution.setStatus(ReportExecStatus.STARTED); + execution.setStartDate(new Date()); + execution.setReport(report); + execution = reportExecDAO.save(execution); + + report.addExec(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(); + handler = tFactory.newTransformerHandler(); + Transformer serializer = handler.getTransformer(); + serializer.setOutputProperty(OutputKeys.ENCODING, SyncopeConstants.DEFAULT_ENCODING); + 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<Reportlet> reportletClass = + dataBinder.findReportletClassHavingConfClass(reportletConf.getClass()); + if (reportletClass != null) { + Reportlet<ReportletConf> autowired = + (Reportlet<ReportletConf>) ApplicationContextProvider.getBeanFactory(). + createBean(reportletClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false); + autowired.setConf(reportletConf); + + // invoke reportlet + try { + autowired.extract(handler); + } catch (Exception e) { + execution.setStatus(ReportExecStatus.FAILURE); + + Throwable t = e instanceof ReportException + ? e.getCause() + : e; + reportExecutionMessage. + append(ExceptionUtil.getFullStackTrace(t)). + 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(ExceptionUtil.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.setEndDate(new Date()); + reportExecDAO.save(execution); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportXMLConst.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportXMLConst.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportXMLConst.java new file mode 100644 index 0000000..1bbae73 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/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.logic.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/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/Reportlet.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/Reportlet.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/Reportlet.java new file mode 100644 index 0000000..cd6dd25 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/Reportlet.java @@ -0,0 +1,47 @@ +/* + * 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.logic.report; + +import org.apache.syncope.common.lib.report.ReportletConf; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * Interface for all elements that can be embedded in a report. + * + * @see org.apache.syncope.core.persistence.beans.Report + */ +public interface Reportlet<T extends ReportletConf> { + + /** + * Set this reportlet configuration. + * + * @param conf configuration + */ + void setConf(T conf); + + /** + * Actual data extraction for reporting. + * + * @param handler SAX content handler for streaming result + * @throws SAXException if there is any problem in SAX handling + * @throws ReportException if anything goes wrong + */ + void extract(ContentHandler handler) throws SAXException, ReportException; +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportletConfClass.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportletConfClass.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportletConfClass.java new file mode 100644 index 0000000..39f1c16 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/ReportletConfClass.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.logic.report; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.syncope.common.lib.report.ReportletConf; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ReportletConfClass { + + Class<? extends ReportletConf> value(); +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/RoleReportlet.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/RoleReportlet.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/RoleReportlet.java new file mode 100644 index 0000000..5bada5d --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/RoleReportlet.java @@ -0,0 +1,327 @@ +/* + * 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.logic.report; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.report.RoleReportletConf; +import org.apache.syncope.common.lib.report.RoleReportletConf.Feature; +import org.apache.syncope.common.lib.to.AbstractAttributableTO; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.RoleTO; +import org.apache.syncope.common.lib.types.SubjectType; +import org.apache.syncope.core.persistence.api.RoleEntitlementUtil; +import org.apache.syncope.core.persistence.api.dao.EntitlementDAO; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.role.Role; +import org.apache.syncope.core.provisioning.java.data.RoleDataBinderImpl; +import org.apache.syncope.core.misc.search.SearchCondConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +@ReportletConfClass(RoleReportletConf.class) +public class RoleReportlet extends AbstractReportlet<RoleReportletConf> { + + private static final int PAGE_SIZE = 10; + + @Autowired + private EntitlementDAO entitlementDAO; + + @Autowired + private RoleDAO roleDAO; + + @Autowired + private SubjectSearchDAO searchDAO; + + @Autowired + private RoleDataBinderImpl roleDataBinder; + + private List<Role> getPagedRoles(final int page) { + final Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()); + final List<Role> result; + if (StringUtils.isBlank(conf.getMatchingCond())) { + result = roleDAO.findAll(); + } else { + result = searchDAO.search(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()), + page, PAGE_SIZE, Collections.<OrderByClause>emptyList(), SubjectType.ROLE); + } + + return result; + } + + private int count() { + Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()); + + return StringUtils.isBlank(conf.getMatchingCond()) + ? roleDAO.findAll().size() + : searchDAO.count(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()), SubjectType.ROLE); + } + + private void doExtractResources(final ContentHandler handler, final AbstractSubjectTO subjectTO) + throws SAXException { + + if (subjectTO.getResources().isEmpty()) { + LOG.debug("No resources found for {}[{}]", subjectTO.getClass().getSimpleName(), subjectTO.getKey()); + } else { + AttributesImpl atts = new AttributesImpl(); + handler.startElement("", "", "resources", null); + + for (String resourceName : subjectTO.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 AbstractAttributableTO attributableTO, + 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 = attributableTO.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, + attributableTO.getClass().getSimpleName(), attributableTO.getKey()); + } + + handler.endElement("", "", "attribute"); + } + handler.endElement("", "", "attributes"); + } + + if (!derAttrs.isEmpty()) { + Map<String, AttrTO> derAttrMap = attributableTO.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, + attributableTO.getClass().getSimpleName(), attributableTO.getKey()); + } + + handler.endElement("", "", "derivedAttribute"); + } + handler.endElement("", "", "derivedAttributes"); + } + + if (!virAttrs.isEmpty()) { + Map<String, AttrTO> virAttrMap = attributableTO.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, + attributableTO.getClass().getSimpleName(), attributableTO.getKey()); + } + + handler.endElement("", "", "virtualAttribute"); + } + handler.endElement("", "", "virtualAttributes"); + } + } + + private void doExtract(final ContentHandler handler, final List<Role> roles) + throws SAXException, ReportException { + + AttributesImpl atts = new AttributesImpl(); + for (Role role : roles) { + atts.clear(); + + for (Feature feature : conf.getFeatures()) { + String type = null; + String value = null; + switch (feature) { + case key: + type = ReportXMLConst.XSD_LONG; + value = String.valueOf(role.getKey()); + break; + + case name: + type = ReportXMLConst.XSD_STRING; + value = String.valueOf(role.getName()); + break; + + case roleOwner: + type = ReportXMLConst.XSD_LONG; + value = String.valueOf(role.getRoleOwner()); + break; + + case userOwner: + type = ReportXMLConst.XSD_LONG; + value = String.valueOf(role.getUserOwner()); + break; + + default: + } + + if (type != null && value != null) { + atts.addAttribute("", "", feature.name(), type, value); + } + } + + handler.startElement("", "", "role", atts); + + // Using RoleTO for attribute values, since the conversion logic of + // values to String is already encapsulated there + RoleTO roleTO = roleDataBinder.getRoleTO(role); + + doExtractAttributes(handler, roleTO, conf.getPlainAttrs(), conf.getDerAttrs(), conf.getVirAttrs()); + + if (conf.getFeatures().contains(Feature.entitlements)) { + handler.startElement("", "", "entitlements", null); + + for (String ent : roleTO.getEntitlements()) { + atts.clear(); + + atts.addAttribute("", "", "id", ReportXMLConst.XSD_STRING, String.valueOf(ent)); + + handler.startElement("", "", "entitlement", atts); + handler.endElement("", "", "entitlement"); + } + + handler.endElement("", "", "entitlements"); + } + // to get resources associated to a role + if (conf.getFeatures().contains(Feature.resources)) { + doExtractResources(handler, roleTO); + } + //to get users asscoiated to a role is preferred RoleDAO to RoleTO + if (conf.getFeatures().contains(Feature.users)) { + handler.startElement("", "", "users", null); + + for (Membership memb : roleDAO.findMemberships(role)) { + atts.clear(); + + atts.addAttribute("", "", "key", ReportXMLConst.XSD_LONG, + String.valueOf(memb.getUser().getKey())); + atts.addAttribute("", "", "username", ReportXMLConst.XSD_STRING, + String.valueOf(memb.getUser().getUsername())); + + handler.startElement("", "", "user", atts); + handler.endElement("", "", "user"); + } + + handler.endElement("", "", "users"); + } + + handler.endElement("", "", "role"); + } + } + + 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("", "", "roleAttributes", atts); + + 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("", "", "roleAttributes"); + handler.endElement("", "", "configurations"); + } + + @Override + protected void doExtract(final ContentHandler handler) throws SAXException, ReportException { + doExtractConf(handler); + for (int i = 1; i <= (count() / PAGE_SIZE) + 1; i++) { + doExtract(handler, getPagedRoles(i)); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/StaticReportlet.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/StaticReportlet.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/StaticReportlet.java new file mode 100644 index 0000000..196f3e9 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/StaticReportlet.java @@ -0,0 +1,120 @@ +/* + * 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.logic.report; + +import org.apache.syncope.common.lib.report.StaticReportletConf; +import org.apache.syncope.core.misc.DataFormat; +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<StaticReportletConf> { + + 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 + public void doExtract(final ContentHandler handler) throws SAXException, ReportException { + + doExtractConf(handler); + + if (StringUtils.hasText(conf.getStringField())) { + handler.startElement("", "", "string", null); + handler.characters(conf.getStringField().toCharArray(), 0, conf.getStringField().length()); + handler.endElement("", "", "string"); + } + + if (conf.getLongField() != null) { + handler.startElement("", "", "long", null); + String printed = String.valueOf(conf.getLongField()); + handler.characters(printed.toCharArray(), 0, printed.length()); + handler.endElement("", "", "long"); + } + + if (conf.getDoubleField() != null) { + handler.startElement("", "", "double", null); + String printed = String.valueOf(conf.getDoubleField()); + handler.characters(printed.toCharArray(), 0, printed.length()); + handler.endElement("", "", "double"); + } + + if (conf.getDateField() != null) { + handler.startElement("", "", "date", null); + String printed = DataFormat.format(conf.getDateField()); + handler.characters(printed.toCharArray(), 0, printed.length()); + handler.endElement("", "", "date"); + } + + if (conf.getTraceLevel() != null) { + handler.startElement("", "", "enum", null); + String printed = conf.getTraceLevel().name(); + handler.characters(printed.toCharArray(), 0, printed.length()); + handler.endElement("", "", "enum"); + } + + if (conf.getListField() != null && !conf.getListField().isEmpty()) { + handler.startElement("", "", "list", null); + for (String item : conf.getListField()) { + if (StringUtils.hasText(item)) { + handler.startElement("", "", "string", null); + handler.characters(item.toCharArray(), 0, item.length()); + handler.endElement("", "", "string"); + } + } + handler.endElement("", "", "list"); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/TextSerializer.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/TextSerializer.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/TextSerializer.java new file mode 100644 index 0000000..3805963 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/TextSerializer.java @@ -0,0 +1,101 @@ +/* + * 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.logic.report; + +import org.apache.cocoon.sax.component.XMLSerializer; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +/** + * Converts XML into plain text. It omits all XML tags and writes only character events to the output. Input document + * must have at least one element - root element - which should wrap all the text inside it. + * + */ +public class TextSerializer extends XMLSerializer { + + private static final String UTF_8 = "UTF-8"; + + private static final String TXT = "text"; + + public TextSerializer() { + super(); + super.setOmitXmlDeclaration(true); + } + + @Override + public void setDocumentLocator(final Locator locator) { + // nothing + } + + @Override + public void processingInstruction(final String target, final String data) + throws SAXException { + // nothing + } + + @Override + public void startDTD(final String name, final String publicId, final String systemId) + throws SAXException { + // nothing + } + + @Override + public void endDTD() throws SAXException { + // nothing + } + + @Override + public void startElement(final String uri, final String loc, final String raw, final Attributes atts) + throws SAXException { + // nothing + } + + @Override + public void endElement(final String uri, final String name, final String raw) + throws SAXException { + // nothing + } + + @Override + public void endDocument() throws SAXException { + super.endDocument(); + } + + /** + * @throws SAXException if text is encountered before root element. + */ + @Override + public void characters(final char buffer[], final int start, final int len) throws SAXException { + super.characters(buffer, start, len); + } + + @Override + public void recycle() { + super.recycle(); + } + + public static TextSerializer createPlainSerializer() { + final TextSerializer serializer = new TextSerializer(); + serializer.setContentType("text/plain; charset=" + UTF_8); + serializer.setEncoding(UTF_8); + serializer.setMethod(TXT); + return serializer; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/UserReportlet.java ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/UserReportlet.java b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/UserReportlet.java new file mode 100644 index 0000000..76efe89 --- /dev/null +++ b/syncope620/core/logic/src/main/java/org/apache/syncope/core/logic/report/UserReportlet.java @@ -0,0 +1,359 @@ +/* + * 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.logic.report; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.report.UserReportletConf; +import org.apache.syncope.common.lib.report.UserReportletConf.Feature; +import org.apache.syncope.common.lib.to.AbstractAttributableTO; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.SubjectType; +import org.apache.syncope.core.persistence.api.RoleEntitlementUtil; +import org.apache.syncope.core.persistence.api.dao.EntitlementDAO; +import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.misc.search.SearchCondConverter; +import org.apache.syncope.core.misc.DataFormat; +import org.apache.syncope.core.provisioning.api.data.RoleDataBinder; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.springframework.beans.factory.annotation.Autowired; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +@ReportletConfClass(UserReportletConf.class) +public class UserReportlet extends AbstractReportlet<UserReportletConf> { + + private static final int PAGE_SIZE = 10; + + @Autowired + private EntitlementDAO entitlementDAO; + + @Autowired + private UserDAO userDAO; + + @Autowired + private SubjectSearchDAO searchDAO; + + @Autowired + private UserDataBinder userDataBinder; + + @Autowired + private RoleDataBinder roleDataBinder; + + private List<User> getPagedUsers(final int page) { + final Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()); + + final List<User> result; + if (StringUtils.isBlank(conf.getMatchingCond())) { + result = userDAO.findAll(adminRoleIds, page, PAGE_SIZE); + } else { + result = searchDAO.search(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()), + page, PAGE_SIZE, Collections.<OrderByClause>emptyList(), SubjectType.USER); + } + + return result; + } + + private int count() { + Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()); + + return StringUtils.isBlank(conf.getMatchingCond()) + ? userDAO.count(adminRoleIds) + : searchDAO.count(adminRoleIds, SearchCondConverter.convert(conf.getMatchingCond()), SubjectType.USER); + } + + private void doExtractResources(final ContentHandler handler, final AbstractSubjectTO subjectTO) + throws SAXException { + + if (subjectTO.getResources().isEmpty()) { + LOG.debug("No resources found for {}[{}]", subjectTO.getClass().getSimpleName(), subjectTO.getKey()); + } else { + AttributesImpl atts = new AttributesImpl(); + handler.startElement("", "", "resources", null); + + for (String resourceName : subjectTO.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 AbstractAttributableTO attributableTO, + 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 = attributableTO.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, + attributableTO.getClass().getSimpleName(), attributableTO.getKey()); + } + + handler.endElement("", "", "attribute"); + } + handler.endElement("", "", "attributes"); + } + + if (!derAttrs.isEmpty()) { + Map<String, AttrTO> derAttrMap = attributableTO.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, + attributableTO.getClass().getSimpleName(), attributableTO.getKey()); + } + + handler.endElement("", "", "derivedAttribute"); + } + handler.endElement("", "", "derivedAttributes"); + } + + if (!virAttrs.isEmpty()) { + Map<String, AttrTO> virAttrMap = attributableTO.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, + attributableTO.getClass().getSimpleName(), attributableTO.getKey()); + } + + handler.endElement("", "", "virtualAttribute"); + } + handler.endElement("", "", "virtualAttributes"); + } + } + + private void doExtract(final ContentHandler handler, final List<User> users) + throws SAXException, ReportException { + + AttributesImpl atts = new AttributesImpl(); + for (User user : users) { + atts.clear(); + + for (Feature feature : conf.getFeatures()) { + String type = null; + String value = null; + switch (feature) { + case key: + type = ReportXMLConst.XSD_LONG; + value = String.valueOf(user.getKey()); + break; + + case username: + type = ReportXMLConst.XSD_STRING; + value = user.getUsername(); + break; + + case workflowId: + type = ReportXMLConst.XSD_LONG; + value = String.valueOf(user.getWorkflowId()); + break; + + case status: + type = ReportXMLConst.XSD_STRING; + value = user.getStatus(); + break; + + case creationDate: + type = ReportXMLConst.XSD_DATETIME; + value = user.getCreationDate() == null + ? "" + : DataFormat.format(user.getCreationDate()); + break; + + case lastLoginDate: + type = ReportXMLConst.XSD_DATETIME; + value = user.getLastLoginDate() == null + ? "" + : DataFormat.format(user.getLastLoginDate()); + break; + + case changePwdDate: + type = ReportXMLConst.XSD_DATETIME; + value = user.getChangePwdDate() == null + ? "" + : DataFormat.format(user.getChangePwdDate()); + break; + + case passwordHistorySize: + type = ReportXMLConst.XSD_INT; + value = String.valueOf(user.getPasswordHistory().size()); + break; + + case failedLoginCount: + type = ReportXMLConst.XSD_INT; + value = String.valueOf(user.getFailedLogins()); + break; + + default: + } + + if (type != null && value != null) { + atts.addAttribute("", "", feature.name(), type, value); + } + } + + handler.startElement("", "", "user", atts); + + // Using UserTO for attribute values, since the conversion logic of + // values to String is already encapsulated there + UserTO userTO = userDataBinder.getUserTO(user); + + doExtractAttributes(handler, userTO, conf.getPlainAttrs(), conf.getDerAttrs(), conf.getVirAttrs()); + + if (conf.getFeatures().contains(Feature.memberships)) { + handler.startElement("", "", "memberships", null); + + for (MembershipTO memb : userTO.getMemberships()) { + atts.clear(); + + atts.addAttribute("", "", "id", ReportXMLConst.XSD_LONG, String.valueOf(memb.getKey())); + atts.addAttribute("", "", "roleId", ReportXMLConst.XSD_LONG, String.valueOf(memb.getRoleId())); + atts.addAttribute("", "", "roleName", ReportXMLConst.XSD_STRING, String.valueOf(memb.getRoleName())); + handler.startElement("", "", "membership", atts); + + doExtractAttributes(handler, memb, memb.getPlainAttrMap().keySet(), memb.getDerAttrMap() + .keySet(), memb.getVirAttrMap().keySet()); + + if (conf.getFeatures().contains(Feature.resources)) { + Membership actualMemb = user.getMembership(memb.getRoleId()); + if (actualMemb == null) { + LOG.warn("Unexpected: cannot find membership for role {} for user {}", memb.getRoleId(), + user); + } else { + doExtractResources(handler, roleDataBinder.getRoleTO(actualMemb.getRole())); + } + } + + handler.endElement("", "", "membership"); + } + + handler.endElement("", "", "memberships"); + } + + if (conf.getFeatures().contains(Feature.resources)) { + doExtractResources(handler, userTO); + } + + handler.endElement("", "", "user"); + } + } + + private void doExtractConf(final ContentHandler handler) throws SAXException { + + AttributesImpl atts = new AttributesImpl(); + handler.startElement("", "", "configurations", null); + handler.startElement("", "", "userAttributes", atts); + + 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("", "", "userAttributes"); + handler.endElement("", "", "configurations"); + } + + @Override + protected void doExtract(final ContentHandler handler) throws SAXException, ReportException { + doExtractConf(handler); + for (int i = 1; i <= (count() / PAGE_SIZE) + 1; i++) { + doExtract(handler, getPagedUsers(i)); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/resources/logic.properties ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/resources/logic.properties b/syncope620/core/logic/src/main/resources/logic.properties new file mode 100644 index 0000000..72f5b06 --- /dev/null +++ b/syncope620/core/logic/src/main/resources/logic.properties @@ -0,0 +1,18 @@ +# 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. +attributableTransformer=org.apache.syncope.core.provisioning.java.DefaultAttributableTransformer +logicInvocationHandler=org.apache.syncope.core.logic.LogicInvocationHandler http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/resources/logicContext.xml ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/resources/logicContext.xml b/syncope620/core/logic/src/main/resources/logicContext.xml new file mode 100644 index 0000000..61eb724 --- /dev/null +++ b/syncope620/core/logic/src/main/resources/logicContext.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:aop="http://www.springframework.org/schema/aop" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/aop + http://www.springframework.org/schema/aop/spring-aop.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context.xsd"> + + <bean id="version" class="java.lang.String"> + <constructor-arg value="${syncope.version}"/> + </bean> + + <aop:aspectj-autoproxy/> + + <context:component-scan base-package="org.apache.syncope.core.logic"/> + + <bean class="${logicInvocationHandler}"/> + <bean class="${attributableTransformer}"/> + +</beans> http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/resources/report/report2csv.xsl ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/resources/report/report2csv.xsl b/syncope620/core/logic/src/main/resources/report/report2csv.xsl new file mode 100644 index 0000000..b1e2c71 --- /dev/null +++ b/syncope620/core/logic/src/main/resources/report/report2csv.xsl @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + + <xsl:import href="userReportlet2csv.xsl"/> + <xsl:import href="roleReportlet2csv.xsl"/> + <xsl:import href="staticReportlet2csv.xsl"/> + + <xsl:param name="status"/> + <xsl:param name="message"/> + <xsl:param name="startDate"/> + <xsl:param name="endDate"/> + + <xsl:template match="/"> + <xsl:apply-templates/> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/resources/report/report2fo.xsl ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/resources/report/report2fo.xsl b/syncope620/core/logic/src/main/resources/report/report2fo.xsl new file mode 100644 index 0000000..7da9cab --- /dev/null +++ b/syncope620/core/logic/src/main/resources/report/report2fo.xsl @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + version="1.0"> + + <xsl:import href="userReportlet2fo.xsl"/> + <xsl:import href="roleReportlet2fo.xsl"/> + <xsl:import href="staticReportlet2fo.xsl"/> + + <xsl:param name="status"/> + <xsl:param name="message"/> + <xsl:param name="startDate"/> + <xsl:param name="endDate"/> + + <xsl:template match="/"> + <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" font-family="Helvetica" font-size="10pt"> + + <!-- defines the layout master --> + <fo:layout-master-set> + <fo:simple-page-master master-name="first" page-height="29.7cm" page-width="21cm" + margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm"> + <fo:region-body margin-top="1cm"/> + <fo:region-before extent="1cm"/> + <fo:region-after extent="1.5cm"/> + </fo:simple-page-master> + </fo:layout-master-set> + + <!-- starts actual layout --> + <fo:page-sequence master-reference="first"> + + <fo:flow flow-name="xsl-region-body"> + <fo:block font-size="24pt" font-weight="bold" text-align="center" space-after="1cm"> + Apache Syncope Report - <xsl:value-of select="report/@name"/> + </fo:block> + + <fo:table table-layout="fixed" border-width="0.5mm" border-style="solid" width="100%" space-after="1cm"> + <fo:table-column column-width="proportional-column-width(1)"/> + <fo:table-column column-width="proportional-column-width(1)"/> + <fo:table-body> + <fo:table-row> + <fo:table-cell> + <fo:block font-size="18pt" font-weight="bold">Report Name:</fo:block> + </fo:table-cell> + <fo:table-cell> + <fo:block font-size="18pt" font-weight="bold"> + <xsl:value-of select="report/@name"/> + </fo:block> + </fo:table-cell> + </fo:table-row> + <fo:table-row> + <fo:table-cell> + <fo:block font-size="18pt" font-weight="bold">Start Date:</fo:block> + </fo:table-cell> + <fo:table-cell> + <fo:block font-size="18pt" font-weight="bold"> + <xsl:value-of select="$startDate"/> + </fo:block> + </fo:table-cell> + </fo:table-row> + <fo:table-row> + <fo:table-cell> + <fo:block font-size="18pt" font-weight="bold">End Date:</fo:block> + </fo:table-cell> + <fo:table-cell> + <fo:block font-size="18pt" font-weight="bold"> + <xsl:value-of select="$endDate"/> + </fo:block> + </fo:table-cell> + </fo:table-row> + </fo:table-body> + </fo:table> + + <xsl:apply-templates/> + </fo:flow> + </fo:page-sequence> + </fo:root> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/resources/report/report2html.xsl ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/resources/report/report2html.xsl b/syncope620/core/logic/src/main/resources/report/report2html.xsl new file mode 100644 index 0000000..c1d6b67 --- /dev/null +++ b/syncope620/core/logic/src/main/resources/report/report2html.xsl @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + + <xsl:import href="userReportlet2html.xsl"/> + <xsl:import href="roleReportlet2html.xsl"/> + <xsl:import href="staticReportlet2html.xsl"/> + + <xsl:param name="status"/> + <xsl:param name="message"/> + <xsl:param name="startDate"/> + <xsl:param name="endDate"/> + + <xsl:template match="/"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <title>Apache Syncope Report - <xsl:value-of select="report/@name"/></title> + </head> + <body> + <table style="border: 1px solid black;"> + <tr> + <td> + <h1>Report Name:</h1> + </td> + <td> + <h1> + <xsl:value-of select="report/@name"/> + </h1> + </td> + </tr> + <tr> + <td> + <h2>Start Date:</h2> + </td> + <td> + <h2> + <xsl:value-of select="$startDate"/> + </h2> + </td> + </tr> + <tr> + <td> + <h2>End Date:</h2> + </td> + <td> + <h2> + <xsl:value-of select="$endDate"/> + </h2> + </td> + </tr> + </table> + + <xsl:apply-templates/> + </body> + </html> + </xsl:template> + +</xsl:stylesheet> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/logic/src/main/resources/report/roleReportlet2csv.xsl ---------------------------------------------------------------------- diff --git a/syncope620/core/logic/src/main/resources/report/roleReportlet2csv.xsl b/syncope620/core/logic/src/main/resources/report/roleReportlet2csv.xsl new file mode 100644 index 0000000..ad092d5 --- /dev/null +++ b/syncope620/core/logic/src/main/resources/report/roleReportlet2csv.xsl @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + + <xsl:variable name="delimiter" select="';'"/> + + <xsl:template match="reportlet[@class='org.apache.syncope.core.report.RoleReportlet']"> + + <xsl:call-template name="header"> + <xsl:with-param name="node" select="configurations/roleAttributes"/> + </xsl:call-template> + <xsl:for-each select="role"> + <xsl:call-template name="roleAttributes"> + <xsl:with-param name="header" select="../configurations/roleAttributes"/> + <xsl:with-param name="attrs" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:for-each> + </xsl:template> + + <xsl:template name="header"> + <xsl:param name="node"/> + <xsl:for-each select="$node/*"> + <xsl:value-of select="text()"/> + <xsl:if test="position() != last()"> + <xsl:value-of select="$delimiter"/> + </xsl:if> + </xsl:for-each> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template name="roleAttributes"> + <xsl:param name="header"/> + <xsl:param name="attrs"/> + + <xsl:for-each select="$header/*"> + <xsl:variable name="nameAttr" select="text()"/> + + <xsl:choose> + <xsl:when test="string-length($attrs/@*[name()=$nameAttr]) > 0"> + <xsl:variable name="roleAttr" select="$attrs/@*[name()=$nameAttr]"/> + <xsl:text>"</xsl:text> + <xsl:value-of select="$roleAttr/."/> + <xsl:text>"</xsl:text> + </xsl:when> + <xsl:when test="name($attrs/*[name(.)=$nameAttr]/*[name(.)='entitlement']) + and count($attrs/*[name(.)=$nameAttr]/node()) > 0"> + <xsl:text>"</xsl:text> + <xsl:for-each select="$attrs/*/entitlement"> + <xsl:variable name="value" select="@id"/> + <xsl:value-of select="$value"/> + <xsl:if test="position() != last()"> + <xsl:value-of select="$delimiter"/> + </xsl:if> + </xsl:for-each> + <xsl:text>"</xsl:text> + </xsl:when> + <xsl:when test="name($attrs/*[name(.)=$nameAttr]/*[name(.)='resource']) + and count($attrs/*[name(.)=$nameAttr]/node()) > 0"> + <xsl:text>"</xsl:text> + <xsl:for-each select="$attrs/*/resource"> + <xsl:variable name="value" select="@name"/> + <xsl:value-of select="$value"/> + <xsl:if test="position() != last()"> + <xsl:value-of select="$delimiter"/> + </xsl:if> + </xsl:for-each> + <xsl:text>"</xsl:text> + </xsl:when> + <xsl:when test="name($attrs/*[name(.)=$nameAttr]/*[name(.)='user']) + and count($attrs/*[name(.)=$nameAttr]/node()) > 0"> + <xsl:text>"</xsl:text> + <xsl:for-each select="$attrs/*/user"> + <xsl:variable name="value" select="@userUsername"/> + <xsl:value-of select="$value"/> + <xsl:if test="position() != last()"> + <xsl:value-of select="$delimiter"/> + </xsl:if> + </xsl:for-each> + <xsl:text>"</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>"</xsl:text> + <xsl:if test="string-length($attrs/*/*[@name=$nameAttr]/value/text()) > 0"> + <xsl:variable name="value" select="$attrs/*/*[@name=$nameAttr]/value/text()"/> + <xsl:value-of select="$value"/> + </xsl:if> + <xsl:text>"</xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="position() != last()"> + <xsl:value-of select="$delimiter"/> + </xsl:if> + + </xsl:for-each> + </xsl:template> + +</xsl:stylesheet> +
