package com.nortal.jroad.client.dhl; import java.io.File; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl.AadressType; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl.DhlDokumentType; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl.DokumentDocument; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl.EdastusDocument.Edastus; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl.MetainfoDocument.Metainfo; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl.TagasisideType; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.impl.DhlIdDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.impl.DhlSaabumisaegDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.impl.DhlSaatjaAsutuseNimiDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.impl.DhlSaatjaAsutuseNrDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.impl.DhlSaatjaEpostDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.impl.DhlSaatmisaegDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_manual.impl.KoostajaFailinimiDocumentImpl; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.GetSendStatusResponseTypeUnencoded.Item; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.OccupationType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.SendDocumentsV2RequestType; import com.nortal.jroad.client.dhl.types.ee.sk.digiDoc.v13.SignedDocType; import com.nortal.jroad.client.exception.XRoadServiceConsumptionException; /** * @author ats.uiboupin */ public interface DhlXTeeService { public enum SendStatus { /** Document is sent to DVK, but some recipients have not received it(marked this document as read) */ SENT("saatmisel"), /** Document is sent to DVK and all recipients have received it(marked it read) */ RECEIVED("saadetud"), /** Document is cancelled (deleteOldDocuments service has been called by the DVK administrator) */ CANCELLED("katkestatud"); String statusCode; SendStatus(String statusCode) { this.statusCode = statusCode; } public static SendStatus get(String parameterName) { final SendStatus[] values = SendStatus.values(); for (SendStatus parameter : values) { if (parameter.statusCode.equals(parameterName)) { return parameter; } } throw new IllegalArgumentException("Unknown status: " + parameterName); } /** * @param staatus - string based enum generated from schema * @return SendStatus Enum from string based "Enum" */ public static SendStatus get(Edastus.Staatus.Enum staatus) { return get(staatus.toString()); } @Override public String toString() { return statusCode; } } /** * @return organizations(regNr, name) that are capable of communicating with DVK */ Map<String/* regNr */, String/* name */> getSendingOptions(); /** * @see {@link #sendDocuments(Collection, AadressType[], AadressType, SendDocumentsDokumentCallback)} */ Set<String> sendDocuments(Collection<ContentToSend> contentsToSend, AadressType[] recipients, AadressType sender); /** * Composes and sends document to given recipients. * * @param contentsToSend - wrapper containing input stream of data with file name and mimeType to be assigned to the DVK document to be sent. * @param recipients * @param sender * @param callback - optional callBack implementation to be called by the service method when the document is composed, allowing to change content of * <i>"dokument"</i> as desired. * @return dhl_Id's of the sent document */ Set<String> sendDocuments(Collection<ContentToSend> contentsToSend, AadressType[] recipients, AadressType sender, SendDocumentsDokumentCallback callback, SendDocumentsRequestCallback requestCallback); /** * @param ids - set of id's to be used to query statuses for. * @return list of items containing single document status information. */ List<Item> getSendStatuses(Set<String> ids); /** * @param maxNrOfDocuments maximum number of documents that will be returned with the service call * @return */ ReceivedDocumentsWrapper receiveDocuments(int maxNrOfDocuments); /** * @param receivedDocumentIds - */ void markDocumentsReceived(Collection<String> receivedDocumentIds); /** * Allows to mark documents received lik {@link #markDocumentsReceived(Collection)}, but also enables to add some additional information.<br> * For example when you discover a virus or fail to process the DVK dokument * * @param receivedDocsInfos */ void markDocumentsReceivedV2(Collection<TagasisideType> receivedDocsInfos); // void runSystemCheck(); List<OccupationType> getOccupationList(List<String> institutionRegNrs); /** * Callback interface, that can be used to alter <i>"dokument"</i> element after it has been composed <br> * (just before attaching it to request body and making the service call) */ interface SendDocumentsDokumentCallback { void doWithDocument(DokumentDocument dokumentDocument); } /** * Callback interface, that can be used to alter request body just before making the service call */ interface SendDocumentsRequestCallback { void doWithRequest(SendDocumentsV2RequestType request); } interface GetDvkOrganizationsHelper { String getOrganizationName(String regnr); Map<String, String> getDvkOrganizationsCache(); /** * makes service call to retrieve up-to-date organisations list */ void updateDvkCapableOrganisationsCache(); /** * @param organizationsCache to be used */ void setDvkOrganizationsCache(Map<String, String> organizationsCache); DvkOrganizationsUpdateStrategy getUpdateStrategy(); void setUpdateStrategy(DvkOrganizationsUpdateStrategy updateStrategy); } /** * @author ats.uiboupin * Strategy pattern implementation for deciding whether or not to update DVK capable organizations list */ interface DvkOrganizationsUpdateStrategy { /** * @param cachedOrganisationName - name(or <code>null</code>) fetched from the cache based on organization registry number. * @return true if updating cache should be done when such organization name(or null) is fetched from the cached list, false otherwise */ boolean update4getOrganizationName(String cachedOrganisationName); /** * @param dvkCapableOrganizations * @return */ boolean update4getDvkOrganizationsCache(Map<String, String> dvkCapableOrganizations); /** * Concrete strategy implementation might do decisions based on time when cache was last updated. * * @param lastUpdate - time when the cache was last successfully updated. */ void setLastUpdated(Calendar lastUpdate); } /** * @author ats.uiboupin * Simple strategy implementation, see #update4getRecipientName() and #update4getDvkOrganizationsCache */ class DvkOrganizationsCacheingUpdateStrategy implements DvkOrganizationsUpdateStrategy { private static org.apache.commons.logging.Log log // = org.apache.commons.logging.LogFactory.getLog(DhlXTeeService.DvkOrganizationsCacheingUpdateStrategy.class); private Calendar lastUpdated; private int maxUpdateInterval = 24 * 60; // 24h private int timeUnit = Calendar.HOUR; /** * Update only if given <code>cachedOrganisationName</code> is not empty. */ public boolean update4getOrganizationName(String cachedOrganisationName) { return StringUtils.isBlank(cachedOrganisationName); // only update when organisation name not found } /** * asks to update cache when asking if last update was more than <code>maxUpdateInterval</code> minutes ago. */ public boolean update4getDvkOrganizationsCache(Map<String, String> dvkCapableOrganizations) { Calendar cal = Calendar.getInstance(); cal.add(timeUnit, -maxUpdateInterval); return dvkCapableOrganizations == null || dvkCapableOrganizations.isEmpty() || lastUpdated == null || lastUpdated.before(cal); } // START: Getters / setters public int getMaxUpdateInterval() { return maxUpdateInterval; } public DvkOrganizationsCacheingUpdateStrategy setMaxUpdateInterval(int maxUpdateInterval) { this.maxUpdateInterval = maxUpdateInterval; return this; } public int getTimeUnit() { return timeUnit; } /** * @param timeUnit must be Calendar field(i.e. Calendar.MINUTE) * @return */ public DvkOrganizationsCacheingUpdateStrategy setTimeUnit(int timeUnit) { this.timeUnit = timeUnit; return this; } public void setLastUpdated(Calendar date) { if (log.isDebugEnabled()) { log.debug("cache updated at: " + (new SimpleDateFormat().format(date.getTime()))); } this.lastUpdated = date; } // END: Getters / setters } public interface ReceivedDocumentsWrapper extends Map<String, ReceivedDocumentsWrapper.ReceivedDocument>, Iterable<String> { public Iterator<String> iterator(); public Set<Entry<String, ReceivedDocument>> entrySet(); public Map<String, ReceivedDocument> getDhlDocumentsMap(); public List<DhlDokumentType> getReceivedDocuments(); public interface ReceivedDocument { public DhlDokumentType getDhlDocument(); public SignedDocType getSignedDoc(); public MetainfoHelper getMetaInfoHelper(); } /** * @return file that contains response (dhl:dokument elements from response attachment) if saving documents is enabled, null otherwise */ File getResponseDocumentsXml(); } class ContentToSend { private String fileName; private String mimeType; private InputStream inputStream; public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public String getMimeType() { return mimeType; } public void setMimeType(String mimeType) { this.mimeType = mimeType; } public InputStream getInputStream() { return inputStream; } public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } } /** * Helper class to work around parsing AnyType. <br> * Convenience methods could be created to the subclasses as follows: * * <pre> * public String getDhlId() { * // DhlIdDocumentImpl.class is class for representing "dhl_id" xml-element. * return getObject(DhlIdDocumentImpl.class).getDhlId(); * } * </pre> * * @author ats.uiboupin * @see http://www.ws-i.org/Profiles/BasicProfile-1.2.html#soapenc_Array */ class AnyTypeHelper { private static Log log = LogFactory.getLog(AnyTypeHelper.class); private final Map<Class<? extends XmlObject>, List<? extends XmlObject>> subElementsList; public AnyTypeHelper(XmlObject xmlObject) { subElementsList = parse(xmlObject); // NOPMD } protected Map<Class<? extends XmlObject>, List<? extends XmlObject>> parse(XmlObject xmlObject) { if (log.isTraceEnabled()) { log.trace("starting to parse xmlObject:\n" + xmlObject + "\n\n"); } XmlCursor cursor = xmlObject.newCursor(); cursor.toFirstChild();// move before the root element cursor.toFirstChild();// move to the first child of the root Map<Class<? extends XmlObject>, List<? extends XmlObject>> subElementsList = new HashMap<Class<? extends XmlObject>, List<? extends XmlObject>>(); try { do { XmlObject object = XmlObject.Factory.parse(cursor.getDomNode()); @SuppressWarnings("unchecked") // to be able to add object that is in fact a subclass of XmlObject, but referenced using XmlObject List<XmlObject> list = (List<XmlObject>) subElementsList.get(object.getClass()); if (list == null) { list = new ArrayList<XmlObject>(); } list.add(object); subElementsList.put(object.getClass(), list); log.debug("adding node: '" + cursor.getDomNode().getLocalName() + "' (" + cursor.getDomNode().getNamespaceURI() + "),\nclass: " + object.getClass() + ", cursor: \n" + object); } while (cursor.toNextSibling()); } catch (XmlException e) { throw new RuntimeException("Failed to parse xmlObject:\n" + xmlObject, e); } return subElementsList; } /** * Low-level method to retrieve any subelement by class * * @param <T> * @param clazz - class of the subelement * @return subelement according to given class, null otherwise */ public <T> T getObject(Class<T> clazz) { @SuppressWarnings("unchecked") List<T> objects = (List<T>) subElementsList.get(clazz); if (objects != null) { return objects.get(0); } return null; } @SuppressWarnings("unchecked") public <T> List<T> getObjects(Class<T> clazz) { return (List<T>) subElementsList.get(clazz); } } class MetainfoHelper extends AnyTypeHelper { public MetainfoHelper(Metainfo metainfo) { super(metainfo); } // START: Convenience methods to get values of subelements // START: methods for the fields created AUTOMATICALLY by DVK public String getDhlId() { return getObject(DhlIdDocumentImpl.class).getDhlId(); } public String getDhlSaatjaAsutuseNr() { return getObject(DhlSaatjaAsutuseNrDocumentImpl.class).getDhlSaatjaAsutuseNr(); } public String getDhlSaatjaAsutuseNimi() { return getObject(DhlSaatjaAsutuseNimiDocumentImpl.class).getDhlSaatjaAsutuseNimi(); } /** * @return the time when DVK received the document */ public Calendar getDhlSaabumisAeg() { return getObject(DhlSaabumisaegDocumentImpl.class).getDhlSaabumisaeg(); } /** * @return the time when DVK sent out the document */ public Calendar getDhlSaatmisAeg() { return getObject(DhlSaatmisaegDocumentImpl.class).getDhlSaatmisaeg(); } public String getDhlSaatjaEpost() { final DhlSaatjaEpostDocumentImpl object = getObject(DhlSaatjaEpostDocumentImpl.class); return object == null ? null : object.getDhlSaatjaEpost(); } /** * XXX: sarnaselt eelnevatele on võimalik kätte saada(automaatselt lisatavatest andmetest) veel näiteks: * dhl_saabumisviis, dhl_saatmisviis, dhl_saatja_asutuse_nimi, dhl_saatja_isikukood, dhl_saaja_asutuse_nr, dhl_saaja_asutuse_nimi, dhl_saaja_isikukood, * dhl_saaja_epost, dhl_kaust */ // END: methods for the fields created AUTOMATICALLY by DVK // START: methods for the fields created MANUALLY by DVK public String getKoostajaFailinimi() { final KoostajaFailinimiDocumentImpl kfDoc = getObject(KoostajaFailinimiDocumentImpl.class); if (null != kfDoc) { return kfDoc.getKoostajaFailinimi().xmlText(); } return null; } // END: methods for the fields created MANUALLY by DVK // END: Convenience methods to get values of subelements } /** * Wrapper(unchecked exception) for checked exception XRoadServiceConsumptionException * * @author Ats Uiboupin */ public class WrappedXRoadServiceConsumptionException extends RuntimeException { private static final long serialVersionUID = 1L; public WrappedXRoadServiceConsumptionException(XRoadServiceConsumptionException cause) { super(resolveMessage(cause), cause); } @Override public XRoadServiceConsumptionException getCause() { return (XRoadServiceConsumptionException) super.getCause(); } private static String resolveMessage(XRoadServiceConsumptionException e) { return "Failed to execute xtee query " + e.getFullServiceName() + ". Fault(" + e.getFaultCode() + "): '" + e.getFaultString() + "'"; } } // START: getters/setters GetDvkOrganizationsHelper getDvkOrganizationsHelper(); void setDvkOrganizationsHelper(GetDvkOrganizationsHelper dvkOrganizationsHelper); // END: getters/setters }