package com.nortal.jroad.client.dhl; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.StringReader; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ProxyInputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ws.commons.util.Base64; import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.mime.Attachment; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import ee.sk.digidoc.SignedDoc; 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.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.TransportDocument.Transport; import com.nortal.jroad.client.dhl.types.ee.riik.schemas.dhl_meta_automatic.DhlDokIDType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.DocumentRefsArrayType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.GetSendStatusRequestType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.GetSendStatusResponseTypeUnencoded; 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.GetSendingOptionsV2RequestType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.InstitutionArrayType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.InstitutionRefsArrayType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.InstitutionType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.MarkDocumentsReceivedRequestType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.MarkDocumentsReceivedRequestTypeUnencoded; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.MarkDocumentsReceivedResponseType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.OccupationArrayType; 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.ReceiveDocumentsRequestType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.ReceiveDocumentsResponseTypeUnencoded; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.SendDocumentsResponseType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.SendDocumentsV2RequestType; import com.nortal.jroad.client.dhl.types.ee.riik.xtee.dhl.producers.producer.dhl.TagasisideArrayType; import com.nortal.jroad.client.dhl.types.ee.sk.digiDoc.v13.DataFileType; import com.nortal.jroad.client.dhl.types.ee.sk.digiDoc.v13.SignedDocType; import com.nortal.jroad.client.exception.XRoadServiceConsumptionException; import com.nortal.jroad.client.service.XRoadDatabaseService; import com.nortal.jroad.client.service.extractor.CustomExtractor; import com.nortal.jroad.model.XRoadAttachment; import com.nortal.jroad.model.XRoadMessage; import com.nortal.jroad.model.XmlBeansXRoadMessage; import com.nortal.jroad.util.AttachmentUtil; /** * @author Ats Uiboupin */ public class DhlXTeeServiceImpl extends XRoadDatabaseService implements DhlXTeeService { private static Log log = LogFactory.getLog(DhlXTeeServiceImpl.class); // START: XTEE DVK service names and versions private static final String SEND_DOCUMENTS = "sendDocuments"; private static final String SEND_DOCUMENTS_VERSION = "v2"; private static final String GET_SENDING_OPTIONS = "getSendingOptions"; private static final String GET_SENDING_OPTIONS_VERSION = "v2"; private static final String GET_OCCUPATION_LIST = "getOccupationList"; private static final String GET_OCCUPATION_LIST_VERSION = "v1"; private static final String XTEE_METHOD_GET_SEND_STATUS = "getSendStatus"; private static final String GET_SEND_STATUS_VERSION = "v1"; private static final String RECEIVE_DOCUMENTS = "receiveDocuments"; private static final String RECEIVE_DOCUMENTS_VERSION = "v2";// v3 existed, but removed from wsdl, as it didn't work private static final String MARK_DOCUMENTS_RECEIVED = "markDocumentsReceived"; private static final String MARK_DOCUMENTS_RECEIVED_VERSION = "v1"; private static final String MARK_DOCUMENTS_RECEIVED_VERSION_2 = "v2"; // END: XTEE DVK service names and versions protected static final String DVK_MESSAGE_CHARSET = "UTF-8"; private GetDvkOrganizationsHelper dvkOrganizationsHelper; private final SendDocumentsHelper sendDocumentsHelper; private String receivedDocumentsResponseRootElemName = "dokumendid"; private String receivedDocumentsFolder; private String sentDocumentsFolder; public DhlXTeeServiceImpl() { this(null); } public DhlXTeeServiceImpl(DvkOrganizationsUpdateStrategy updateStrategy) { this.dvkOrganizationsHelper = new GetDvkOrganizationsHelperImpl(); if (updateStrategy != null) { dvkOrganizationsHelper.setUpdateStrategy(updateStrategy); } this.sendDocumentsHelper = this.new SendDocumentsHelper(); } /* * removed from some version of dhl * private static final String RUN_SYSTEM_CHECK = "runSystemCheck"; * private static final String RUN_SYSTEM_CHECK_VERSION = "v1"; * @Override * public void runSystemCheck() { * String queryMethod = getDatabase() + "." + RUN_SYSTEM_CHECK + "." + RUN_SYSTEM_CHECK_VERSION; * RunSystemCheckRequestType request = RunSystemCheckRequestType.Factory.newInstance(); * log.debug("executing " + queryMethod); * try { * XRoadMessage<RunSystemCheckResponseType> response // * = send(new XmlBeansXRoadMessage<RunSystemCheckRequestType>(request), RUN_SYSTEM_CHECK, * RUN_SYSTEM_CHECK_VERSION); * if (!response.getContent().getStringValue().equalsIgnoreCase("OK")) { * throw new RuntimeException("Service didn't respond with 'OK': " + response.getContent().getStringValue()); * } * } catch (XRoadServiceConsumptionException e) { * throw new RuntimeException(resolveMessage(queryMethod, e), e); * } * } */ public void markDocumentsReceived(Collection<String> receivedDocumentIds) { String queryMethod = getDatabase() + "." + MARK_DOCUMENTS_RECEIVED + "." + MARK_DOCUMENTS_RECEIVED_VERSION; MarkDocumentsReceivedRequestType request = MarkDocumentsReceivedRequestType.Factory.newInstance(); byte[] attachmentBody = createMarkDocumentsReceivedAttachmentBody(receivedDocumentIds); request.setDokumendid(null); String cid = AttachmentUtil.getUniqueCid(); XmlCursor cursor = request.newCursor(); cursor.toNextToken(); Element node = (Element) cursor.getDomNode(); node.setAttribute("href", "cid:" + cid); cursor.dispose(); XRoadAttachment attachment = new XRoadAttachment(cid, "{http://www.w3.org/2001/XMLSchema}base64Binary", attachmentBody); log.debug("executing " + queryMethod); try { XRoadMessage<MarkDocumentsReceivedResponseType> response // = send(new XmlBeansXRoadMessage<MarkDocumentsReceivedRequestType>(request, Arrays.asList(attachment)), MARK_DOCUMENTS_RECEIVED, MARK_DOCUMENTS_RECEIVED_VERSION); if (!response.getContent().getStringValue().equalsIgnoreCase("OK")) { throw new RuntimeException("Service didn't respond with 'OK': " + response.getContent().getStringValue()); } } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } } public void markDocumentsReceivedV2(Collection<TagasisideType> receivedDocsInfos) { String queryMethod = getDatabase() + "." + MARK_DOCUMENTS_RECEIVED + "." + MARK_DOCUMENTS_RECEIVED_VERSION_2; MarkDocumentsReceivedRequestType request = MarkDocumentsReceivedRequestType.Factory.newInstance(); byte[] attachmentBody = createMarkDocumentsReceivedV2AttachmentBody(receivedDocsInfos); request.setDokumendid(null); final XRoadAttachment attachment = setDokumendidHrefToAttachment(attachmentBody, request); log.debug("executing " + queryMethod); try { XRoadMessage<MarkDocumentsReceivedResponseType> response // = send(new XmlBeansXRoadMessage<MarkDocumentsReceivedRequestType>(request, Arrays.asList(attachment)), MARK_DOCUMENTS_RECEIVED, MARK_DOCUMENTS_RECEIVED_VERSION_2); if (!response.getContent().getStringValue().equalsIgnoreCase("OK")) { throw new RuntimeException("Service didn't respond with 'OK': " + response.getContent().getStringValue()); } } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } } public ReceivedDocumentsWrapper receiveDocuments(int maxNrOfDocuments) { String queryMethod = getDatabase() + "." + RECEIVE_DOCUMENTS + "." + RECEIVE_DOCUMENTS_VERSION; ReceiveDocumentsRequestType request = ReceiveDocumentsRequestType.Factory.newInstance(); request.setArv(BigInteger.valueOf(maxNrOfDocuments)); log.debug("executing " + queryMethod); try { XRoadMessage<ReceiveDocumentsResponseTypeUnencoded> response // = send(new XmlBeansXRoadMessage<ReceiveDocumentsRequestType>(request), RECEIVE_DOCUMENTS, RECEIVE_DOCUMENTS_VERSION); return new ReceivedDocumentsWrapperImpl(response, maxNrOfDocuments); } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } } static class UnzipThreadVO { private final File file; private OutputStream outputStream; private Thread unzipThread; private InputStreamWithOutputProxy wrappedInputStream; private List<Throwable> ungzipThreadThrowableList = new LinkedList<Throwable>(); public UnzipThreadVO(File file) { this.file = file; } public File getFile() { return file; } public OutputStream getOutputStream() { return outputStream; } public void setOutputStream(OutputStream outputStream) { this.outputStream = outputStream; } public Thread getUnzipThread() { return unzipThread; } public void setUnzipThread(Thread unzipThread) { this.unzipThread = unzipThread; } public List<Throwable> getUngzipThreadThrowableList() { return ungzipThreadThrowableList; } public void setUngzipThreadThrowableList(List<Throwable> ungzipThreadThrowableList) { this.ungzipThreadThrowableList = ungzipThreadThrowableList; } public void setWrappedInputStream(InputStreamWithOutputProxy wrappedInputStream) { this.wrappedInputStream = wrappedInputStream; } public InputStreamWithOutputProxy getWrappedInputStream() { return wrappedInputStream; } } public class ReceivedDocumentsWrapperImpl extends AbstractMap<String, ReceivedDocumentsWrapper.ReceivedDocument> implements ReceivedDocumentsWrapper { private final List<DhlDokumentType> receivedDocuments; private final Map<String /* dhlId */, ReceivedDocument> dhlDocumentsMap; private UnzipThreadVO unzipThreadVO; private File responseXml; private int maxNrOfDocuments; public ReceivedDocumentsWrapperImpl(XRoadMessage<ReceiveDocumentsResponseTypeUnencoded> response, int maxNrOfDocuments) { List<DokumentDocument> dokumentDocuments; try { this.maxNrOfDocuments = maxNrOfDocuments; dokumentDocuments = getTypeFromGzippedAndEncodedSoapArray(getInputStream(response), DokumentDocument.class); } catch (IOException e) { handleResponseFile(); throw new RuntimeException("Failed to get input of attachment ", e); } this.receivedDocuments = new ArrayList<DhlDokumentType>(); for (DokumentDocument dokumentDocument : dokumentDocuments) { receivedDocuments.add(dokumentDocument.getDokument()); } this.dhlDocumentsMap = new HashMap<String, ReceivedDocument>(); log.debug("received " + receivedDocuments.size() + " documents"); for (DhlDokumentType dhlDokument : receivedDocuments) { Metainfo metainfo = dhlDokument.getMetainfo(); MetainfoHelper metaInfoHelper = new MetainfoHelper(metainfo); String dhlId = metaInfoHelper.getDhlId(); dhlDocumentsMap.put(dhlId, new ReceivedDocumentImpl(dhlDokument, dhlId, metaInfoHelper)); } handleResponseFile(); } /** * @param response * @return returns originalInputStream of the response if <code>receivedDocumentsFolder</code> is not set, <br> * otherwise return wrappedInputStream that will write response into file under <code>receivedDocumentsFolder</code> * @throws IOException */ private InputStream getInputStream(XRoadMessage<ReceiveDocumentsResponseTypeUnencoded> response) throws IOException { final InputStream originalInputStream = response.getAttachments().get(0).getInputStream(); if (StringUtils.isBlank(receivedDocumentsFolder)) { return originalInputStream; } final InputStreamWithOutputProxy wrappedInputStream = new InputStreamWithOutputProxy(originalInputStream); log.info("receivedDocumentsFolder=" + receivedDocumentsFolder); final File directory = new File(receivedDocumentsFolder); if (!directory.exists()) { throw new FileNotFoundException("receivedDocumentsFolder '" + receivedDocumentsFolder + "' doesn't exist!"); } this.unzipThreadVO = new UnzipThreadVO(File.createTempFile("dvk", ".xml", directory)); prepareFileOutputStream(wrappedInputStream, unzipThreadVO); return wrappedInputStream; } private void prepareFileOutputStream(final InputStreamWithOutputProxy wrappedInputStream, UnzipThreadVO threadVO) throws FileNotFoundException { final File outputFile = threadVO.getFile(); final FileOutputStream fileOutputStream = new FileOutputStream(outputFile); if (StringUtils.isNotBlank(receivedDocumentsResponseRootElemName)) { try { // add root element - response could contain more than one <dhl:document/> fileOutputStream.write(("<" + receivedDocumentsResponseRootElemName + ">").getBytes()); } catch (IOException e) { IOUtils.closeQuietly(wrappedInputStream); IOUtils.closeQuietly(fileOutputStream); if (!outputFile.delete()) { log.warn("failed to delete file " + outputFile.getAbsolutePath()); } throw new RuntimeException("failed to write start of root xml element to " + outputFile.getAbsolutePath(), e); } } else if (maxNrOfDocuments > 1) { throw new RuntimeException( "Logging response is turned on and response could potentially contain more than 1 document, but receivedDocumentsResponseRootElemName is empty '" + receivedDocumentsResponseRootElemName + "'. To avoid creating invalid xml please set the name for received documents root element (receivedDocumentsResponseRootElemName)!"); } // get's written each time smth is read from wrappedInputStream final PipedOutputStream pipedOutputStreamProxy = new PipedOutputStream(); // kui wrappedInputStream'ist loetakse, siis kogu info edastatakse ka pipedOutputStream'ile wrappedInputStream.setOutputStream(pipedOutputStreamProxy); final List<Throwable> ungzipThreadThrowableList = threadVO.getUngzipThreadThrowableList(); final PipedInputStream pipedInputStream; try { pipedInputStream = new PipedInputStream(pipedOutputStreamProxy); } catch (IOException e1) { IOUtils.closeQuietly(pipedOutputStreamProxy); throw new RuntimeException("failed to unzip input(connecting pipeStream failed)", e1); } Thread ungzipThread = new Thread(new Runnable() { public void run() { try { getUnzipAndDecodeOutputStream(pipedInputStream, fileOutputStream); if (StringUtils.isNotBlank(receivedDocumentsResponseRootElemName)) { try { fileOutputStream.write(("</" + receivedDocumentsResponseRootElemName + ">").getBytes()); } catch (IOException e) { throw new RuntimeException("failed to write end of root xml element to " + outputFile.getAbsolutePath(), e); } } } catch (Throwable t) { log.error("error within unzipthread!", t); ungzipThreadThrowableList.add(t); } finally { IOUtils.closeQuietly(pipedInputStream); IOUtils.closeQuietly(fileOutputStream); } } }); ungzipThread.start(); threadVO.setWrappedInputStream(wrappedInputStream); threadVO.setUnzipThread(ungzipThread); } /** * If <code>receivedDocumentsFolder</code> was set then renames created file based on dhl'ids that were received, <br> * otherwise does nothing */ private void handleResponseFile() { if (unzipThreadVO == null) { return; } File xmlFile = unzipThreadVO.getFile(); syncWithUnzipThread(); final Set<String> keySet = dhlDocumentsMap.keySet(); String newFilePrefix = "dvk_" + (new SimpleDateFormat("yyyy.MM.dd-kk.mm.ss,SSS").format(new Date())) + "_"; if (keySet.size() > 0) { final String dvkIds = keySet.toString().replaceAll("\\s", ""); newFilePrefix = newFilePrefix + dvkIds.substring(1, dvkIds.length() - 1); if (dvkIds.length() > 200) { newFilePrefix = newFilePrefix.substring(0, 200) + "..";// limit length just in case receiving a lot of documents } } responseXml = new File(receivedDocumentsFolder, newFilePrefix + ".xml"); final boolean success = xmlFile.renameTo(responseXml); if (!success) { log.error("renaming file " + responseXml.getAbsolutePath() + " failed. New name would have been '" + responseXml.getAbsolutePath() + "'"); responseXml = xmlFile; } } private void syncWithUnzipThread() { final Thread unzipThread = unzipThreadVO.getUnzipThread(); if (unzipThread != null) { try { final OutputStream outputStream = unzipThreadVO.getWrappedInputStream().getOutputStream(); outputStream.flush(); outputStream.close(); unzipThread.join(); } catch (InterruptedException ie) { throw new RuntimeException("unzip thread was interrupted", ie); } catch (IOException e) { throw new RuntimeException("flushing proxy output stream failed", e); } final List<Throwable> ungzipThreadThrowableList = unzipThreadVO.getUngzipThreadThrowableList(); if (!ungzipThreadThrowableList.isEmpty()) { throw new RuntimeException("ungzip failed", ungzipThreadThrowableList.get(0)); } } } public Iterator<String> iterator() { return dhlDocumentsMap.keySet().iterator(); } public Set<Entry<String, ReceivedDocument>> entrySet() { return dhlDocumentsMap.entrySet(); } // START: getters/setters public Map<String, ReceivedDocument> getDhlDocumentsMap() { return dhlDocumentsMap; } public List<DhlDokumentType> getReceivedDocuments() { return receivedDocuments; } public File getResponseDocumentsXml() { return responseXml; } // END: getters/setters public class ReceivedDocumentImpl implements ReceivedDocument { private final String dhlId; private final MetainfoHelper metaInfoHelper; private final DhlDokumentType dhlDocument; public ReceivedDocumentImpl(DhlDokumentType dhlDocument, String dhlId, MetainfoHelper metaInfoHelper) { this.dhlDocument = dhlDocument; this.dhlId = dhlId; this.metaInfoHelper = metaInfoHelper; } public DhlDokumentType getDhlDocument() { return dhlDocument; } public SignedDocType getSignedDoc() { return dhlDocument.getSignedDoc(); } // START: getters/setters public String getDhlId() { return dhlId; } public MetainfoHelper getMetaInfoHelper() { return metaInfoHelper; } // END: getters/setters } } public Set<String> sendDocuments(Collection<ContentToSend> contentsToSend, AadressType[] recipients, AadressType sender) { return sendDocuments(contentsToSend, recipients, sender, null, null); } public Set<String> sendDocuments(Collection<ContentToSend> contentsToSend, AadressType[] recipients, AadressType sender, SendDocumentsDokumentCallback dokumentCallback, SendDocumentsRequestCallback requestCallback) { String queryMethod = getDatabase() + "." + SEND_DOCUMENTS + "." + SEND_DOCUMENTS_VERSION; DokumentDocument dokumentDocument = sendDocumentsHelper.constructDokumentDocument(contentsToSend, sender, recipients); if (dokumentCallback != null) { dokumentCallback.doWithDocument(dokumentDocument); } byte[] base64Doc = gzipAndEncodeXmlObject(dokumentDocument); SendDocumentsV2RequestType request = SendDocumentsV2RequestType.Factory.newInstance(); request.setDokumendid(null); final XRoadAttachment attachment = setDokumendidHrefToAttachment(base64Doc, request); if (requestCallback != null) { requestCallback.doWithRequest(request); } log.debug("executing " + queryMethod); try { XRoadMessage<SendDocumentsResponseType> response = send(new XmlBeansXRoadMessage<SendDocumentsV2RequestType>(request, Collections .singletonList(attachment)), SEND_DOCUMENTS, SEND_DOCUMENTS_VERSION); List<DhlDokIDType> dokumentDocuments // = getTypeFromGzippedAndEncodedSoapArray(response.getAttachments().get(0).getInputStream(), DhlDokIDType.class); Set<String> sentDocumentsDhlIds = new HashSet<String>(); for (DhlDokIDType dokIDType : dokumentDocuments) { sentDocumentsDhlIds.add(dokIDType.getStringValue()); } return sentDocumentsDhlIds; } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } catch (IOException e) { throw new RuntimeException("Failed to extract response " + queryMethod, e); } } public List<OccupationType> getOccupationList(List<String> institutionRegNrs) { String queryMethod = getDatabase() + "." + GET_OCCUPATION_LIST + "." + GET_OCCUPATION_LIST_VERSION; InstitutionRefsArrayType request = InstitutionRefsArrayType.Factory.newInstance(); for (String regNr : institutionRegNrs) { request.addAsutus(regNr); } log.debug("executing " + queryMethod); try { XRoadMessage<OccupationArrayType> response = send(new XmlBeansXRoadMessage<InstitutionRefsArrayType>(request)// , GET_OCCUPATION_LIST, GET_OCCUPATION_LIST_VERSION); return response.getContent().getAmetikohtList(); } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } } public Map<String/* regNr */, String/* name */> getSendingOptions() { String queryMethod = getDatabase() + "." + GET_SENDING_OPTIONS + "." + GET_SENDING_OPTIONS_VERSION; log.debug("executing " + queryMethod); GetSendingOptionsV2RequestType request = GetSendingOptionsV2RequestType.Factory.newInstance(); try { final XRoadMessage<InstitutionArrayType> response = send(new XmlBeansXRoadMessage<GetSendingOptionsV2RequestType>(request)// , GET_SENDING_OPTIONS, GET_SENDING_OPTIONS_VERSION); if (log.isTraceEnabled()) { log.trace("execution result#1 response:\t" + ToStringBuilder.reflectionToString(response) + "'"); } InstitutionArrayType responseContent = response.getContent(); List<InstitutionType> orgList = responseContent.getAsutusList(); log.debug(orgList.size() + " organisations can use DVK"); HashMap<String, String> dvkCapableOrganizations = new HashMap<String, String>(orgList.size()); for (InstitutionType institutionType : orgList) { dvkCapableOrganizations.put(institutionType.getRegnr(), institutionType.getNimi()); log.debug("\t" + institutionType.getRegnr() + ":\t" + institutionType.getNimi()); } return dvkCapableOrganizations; } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } } public List<Item> getSendStatuses(Set<String> ids) { if (ids == null || ids.size() == 0) { log.warn("No documents sent! sentDocIds=" + ids); return Collections.<Item> emptyList(); } String queryMethod = getDatabase() + "." + XTEE_METHOD_GET_SEND_STATUS + "." + GET_SEND_STATUS_VERSION; try { DocumentRefsArrayType documentRefsArray = DocumentRefsArrayType.Factory.newInstance(); for (String id : ids) { documentRefsArray.addDhlId(id); } String attachmentBodyUnencoded = documentRefsArray.toString(); log.debug("getSendStatus plain attachment body: '" + attachmentBodyUnencoded + "'"); GetSendStatusRequestType request = GetSendStatusRequestType.Factory.newInstance(); XmlCursor cursor = request.newCursor(); String cid = AttachmentUtil.getUniqueCid(); cursor.toNextToken(); cursor.insertAttributeWithValue("href", "cid:" + cid); cursor.dispose(); byte[] base64 = gzipAndEncodeString(attachmentBodyUnencoded); XRoadAttachment attachment = new XRoadAttachment(cid, "{http://www.w3.org/2001/XMLSchema}base64Binary", base64); XRoadMessage<GetSendStatusRequestType> reqMessage = new XmlBeansXRoadMessage<GetSendStatusRequestType>(request, Arrays.asList(attachment)); log.debug("executing " + queryMethod); XRoadMessage<GetSendStatusResponseTypeUnencoded> response = send(reqMessage, XTEE_METHOD_GET_SEND_STATUS, GET_SEND_STATUS_VERSION, null, new GetSendStatusExtractor()); final GetSendStatusResponseTypeUnencoded unencoded = response.getContent(); return unencoded.getItemList(); } catch (XRoadServiceConsumptionException e) { throw new WrappedXRoadServiceConsumptionException(e); } catch (Exception e) { throw new RuntimeException("Failed to execute xtee query " + queryMethod, e); } } private static class GetSendStatusExtractor extends CustomExtractor { public XRoadMessage<GetSendStatusResponseTypeUnencoded> extractData(WebServiceMessage message) throws IOException, TransformerException { Attachment attachment = (Attachment) ((SaajSoapMessage) message).getAttachments().next(); String xml = new String(unzipAndDecode(attachment.getInputStream()), DVK_MESSAGE_CHARSET); final GetSendStatusResponseTypeUnencoded content = getTypeFromXml(addCorrectNamespaces(xml), GetSendStatusResponseTypeUnencoded.class); return new XmlBeansXRoadMessage<GetSendStatusResponseTypeUnencoded>(content); } private String addCorrectNamespaces(String xml) { Node root; try { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(new InputSource(new StringReader(xml))); Document targetDoc = docBuilder.newDocument(); root = targetDoc.appendChild(targetDoc.createElementNS("", "xml-fragment")); Node keha = doc.getFirstChild(); NodeList items = keha.getChildNodes(); for (int i = 0; i < items.getLength(); i++) { Node item = items.item(i); final String name1 = item.getNodeName(); log.debug("name=" + name1); if ("item".equalsIgnoreCase(name1)) { log.debug("parsing item"); Node itemTarget = root.appendChild(targetDoc.createElementNS("", "item")); NodeList sublist = item.getChildNodes(); for (int j = 0; j < sublist.getLength(); j++) { Node subItem = sublist.item(j); final String localName = subItem.getNodeName(); if ("dhl_id".equalsIgnoreCase(localName)) { final String NS_DHL_META_AUTOMATIC = "http://www.riik.ee/schemas/dhl-meta-automatic"; addNameSpace(NS_DHL_META_AUTOMATIC, subItem, itemTarget); } else if ("edastus".equalsIgnoreCase(localName)) { final String NS_SCHEMAS_DHL = "http://www.riik.ee/schemas/dhl"; addNameSpace(NS_SCHEMAS_DHL, subItem, itemTarget); } else if ("olek".equalsIgnoreCase(localName)) { addNameSpace("", subItem, itemTarget); // } else { // log.error("Unexpected localName: '"+localName+"', text:\n'"+subItem.getTextContent()+"'"); } } } } } catch (Exception e) { throw new RuntimeException("failed to add correct namespaces to given xml: " + xml, e); } String resultXml = xmlNodeToString(root); if (log.isDebugEnabled()) { log.debug("Added namespaces for the xml.\n START: xml source:\n" + xml + "\n\nEND: xml source\nSTART: xml result\n" + resultXml + "\n\nEND: xml result"); } return resultXml; } private static String xmlNodeToString(Node node) { try { Source source = new DOMSource(node); StringWriter stringWriter = new StringWriter(); Result result = new StreamResult(stringWriter); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.transform(source, result); return stringWriter.getBuffer().toString(); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } return null; } private void addNameSpace(String ns, Node source, Node target) { Node newElement; if (source.getNodeType() == Node.TEXT_NODE) { final String textContent = source.getTextContent(); log.debug("Creating textnode with content:\n'" + textContent + "'"); newElement = target.getOwnerDocument().createTextNode(textContent); } else { newElement = target.getOwnerDocument().createElementNS(ns, source.getNodeName()); } NodeList childNodes = source.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node childItem = childNodes.item(i); addNameSpace(ns, childItem, newElement); } target.appendChild(newElement); } } /** * @author ats.uiboupin * Since the list of DVK capable organisations grows/changes very rarely (maybe one in a month or even more rarely) * it might be feasible to keep the list cached in memory and for example <br> * updated onece a day by some job, <br> * or use strategy pattern ant set strategy when creating instance of the service class in the top-level class. */ private class GetDvkOrganizationsHelperImpl implements GetDvkOrganizationsHelper { private Map<String/* regNr */, String/* name */> dvkCapableOrganizations; private DvkOrganizationsUpdateStrategy updateStrategy; public Map<String/* regNr */, String/* name */> getDvkOrganizationsCache() { if (getUpdateStrategy().update4getDvkOrganizationsCache(dvkCapableOrganizations)) { updateDvkCapableOrganisationsCache(); } return dvkCapableOrganizations; } public String getOrganizationName(String regnr) { String orgName = getDvkOrganizationsCache().get(regnr); if (getUpdateStrategy().update4getOrganizationName(orgName)) { updateDvkCapableOrganisationsCache(); orgName = getDvkOrganizationsCache().get(regnr); } return orgName; } public void updateDvkCapableOrganisationsCache() { log.info("starting to update dvkCapableOrganisationsCache"); setDvkOrganizationsCache(getSendingOptions()); log.info("updated dvkCapableOrganisationsCache"); } public void setDvkOrganizationsCache(Map<String, String> cache) { this.dvkCapableOrganizations = cache; getUpdateStrategy().setLastUpdated(Calendar.getInstance()); } // START: getters/setters public DvkOrganizationsUpdateStrategy getUpdateStrategy() { if (updateStrategy == null) { log.debug("-- using default cache update strategy--"); updateStrategy = new DhlXTeeService.DvkOrganizationsCacheingUpdateStrategy().setMaxUpdateInterval(24 * 60);// 24h } return updateStrategy; } public void setUpdateStrategy(DvkOrganizationsUpdateStrategy updateStrategy) { this.updateStrategy = updateStrategy; } // END: getters/setters } /** * @author ats.uiboupin * Contains methods used by sendDocuments service. <br> * Current implementation uses {@link GetDvkOrganizationsHelper} to fill receivers names based on registration codes. */ private class SendDocumentsHelper { /** * Creates (Dhl)<Dokument/> element containing info about sender, recipients * and given files in <SignedDoc/> element(aka digiDoc) * * @param contentsToSend * @param sender * @param recipients * @return (Dhl)<Dokument/> XmlObject */ private DokumentDocument constructDokumentDocument(Collection<ContentToSend> contentsToSend, AadressType sender, AadressType[] recipients) { DokumentDocument dokumentDocument = DokumentDocument.Factory.newInstance(); DhlDokumentType dokumentContainer = dokumentDocument.addNewDokument(); // Add mandatory empty elements dokumentContainer.addNewMetaxml(); dokumentContainer.addNewAjalugu(); if (StringUtils.isBlank(sender.getAsutuseNimi())) { String senderName = getDvkOrganizationsHelper().getOrganizationName(sender.getRegnr()); sender.setAsutuseNimi(senderName); log.debug("Added senders name based on organization code from DVK organizations list: '" + senderName + "'"); } Transport transport = dokumentContainer.addNewTransport(); transport.setSaatja(sender); for (AadressType recipient : recipients) { String recipientName = getDvkOrganizationsHelper().getOrganizationName(recipient.getRegnr()); if (StringUtils.isBlank(recipientName)) { throw new IllegalArgumentException("Cannot send documents to recipient with reg.Nr " + recipient.getRegnr() + " using DVK because recipient" + recipient.getRegnr() + " is not DVK capable."); } recipient.setAsutuseNimi(recipientName); } transport.setSaajaArray(recipients); final DataFileType[] dataFiles = addSignedDocToDokument(contentsToSend, dokumentContainer); if (log.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Constructed dokument from: ")// .append(sender.getRegnr()).append("-").append(sender.getAsutuseNimi()).append(" to [");// for (AadressType recipient : recipients) { sb.append(recipient.getRegnr()).append("-").append(recipient.getAsutuseNimi()).append(" | "); } sb.append("] with files: "); for (DataFileType dataFile : dataFiles) { sb.append(dataFile.getFilename()).append("(").append(dataFile.getSize()).append(") | "); } log.debug(sb.toString()); } return dokumentDocument; } /** * Adds <SignedDoc/> element(aka digiDoc) containing given files as <DataFile/> to * (Dhl)<Dokument/> element * * @param contentsToSend - Object with InputStream and name of the file to be used for output and corresponding mimeType * @param dokumentContainer - (Dhl)<Dokument/> element to which <SignedDoc/> should be added as a child * node * @return {@link #getDataFiles(Map)} based on input files */ private DataFileType[] addSignedDocToDokument(Collection<ContentToSend> contentsToSend, DhlDokumentType dokumentContainer) { final DataFileType[] dataFiles = getDataFiles(contentsToSend); SignedDocType signedDoc = dokumentContainer.addNewSignedDoc(); signedDoc.setFormat(SignedDoc.FORMAT_DIGIDOC_XML); signedDoc.setVersion(SignedDoc.VERSION_1_3); signedDoc.setDataFileArray(dataFiles); return dataFiles; } /** * @param contentsToSend - Object with InputStream and name of the file to be used for output and corresponding mimeType * @return array of <DataFile > elements to be included inside the digiDoc envelope called <SignedDoc /> */ private DataFileType[] getDataFiles(Collection<ContentToSend> contentsToSend) { DataFileType[] files = new DataFileType[contentsToSend.size()]; int fileIndex = 0; for (ContentToSend contentToSend : contentsToSend) { DataFileType dataFile = DataFileType.Factory.newInstance(); dataFile.setFilename(contentToSend.getFileName()); dataFile.setId("D" + fileIndex);// spec: Andmefailide tunnused algavad sümboliga 'D', millele järgneb faili järjekorranumber dataFile.setMimeType(contentToSend.getMimeType()); dataFile.setContentType(DataFileType.ContentType.EMBEDDED_BASE_64); final StringWriter stringWriter = new StringWriter(); final OutputStream encodeStream = Base64.newEncoder(stringWriter, 0, null); final InputStream is = contentToSend.getInputStream(); try { long sizeCopied = IOUtils.copyLarge(is, encodeStream); dataFile.setSize(BigDecimal.valueOf(sizeCopied)); } catch (IOException e) { throw new RuntimeException("Failed to get input to the file to be sent", e); } finally { IOUtils.closeQuietly(encodeStream); IOUtils.closeQuietly(is); } dataFile.setStringValue(stringWriter.toString()); files[fileIndex++] = dataFile; } return files; } /** * Writes the document into given stream. Removes namespace usage from SignedDoc element (Digidoc container) Amphora test * environment is not capable of receiving such xml */ private void writeXmlObject(XmlObject xmlObject, OutputStream outputStream) { XmlOptions options = new XmlOptions(); options.setCharacterEncoding(DVK_MESSAGE_CHARSET); { // fix DigiDoc client bug (also present with PostiPoiss doc-management-system) // they don't accept that SignedDoc have nameSpace alias set options.setSavePrettyPrint(); HashMap<String, String> suggestedPrefixes = new HashMap<String, String>(2); suggestedPrefixes.put("http://www.sk.ee/DigiDoc/v1.3.0#", ""); // suggestedPrefixes.put("http://www.sk.ee/DigiDoc/v1.4.0#", ""); options.setSaveSuggestedPrefixes(suggestedPrefixes); } try { xmlObject.save(outputStream, options); writeXmlObjectToSentDocumentsFolder(xmlObject, options); } catch (IOException e) { log.error("Writing document failed", e); throw new java.lang.RuntimeException(e); } } private void writeXmlObjectToSentDocumentsFolder(XmlObject xmlObject, XmlOptions options) throws FileNotFoundException, IOException { OutputStream dvkSentMsgOS = null; try { if (StringUtils.isNotBlank(sentDocumentsFolder)) { final File directory = new File(sentDocumentsFolder); if (!directory.exists()) { throw new FileNotFoundException("receivedDocumentsFolder '" + sentDocumentsFolder + "' doesn't exist!"); } String newFilePrefix = "dvk_" + (new SimpleDateFormat("yyyy.MM.dd-kk.mm.ss,SSS").format(new Date())); File sentFile = new File(directory, "sent_" + newFilePrefix + ".xml"); dvkSentMsgOS = new FileOutputStream(sentFile); xmlObject.save(dvkSentMsgOS, options); } } finally { IOUtils.closeQuietly(dvkSentMsgOS); } } } private byte[] createMarkDocumentsReceivedAttachmentBody(Collection<String> ids) { MarkDocumentsReceivedRequestTypeUnencoded body = MarkDocumentsReceivedRequestTypeUnencoded.Factory.newInstance(); DocumentRefsArrayType documentRefsArray = DocumentRefsArrayType.Factory.newInstance(); for (String id : ids) { documentRefsArray.addDhlId(id); } body.setDokumendid(documentRefsArray); if (log.isTraceEnabled()) { log.trace("createMarkDocumentsReceivedAttachmentBody::" + body); } return gzipAndEncodeString(body.toString()); } private byte[] createMarkDocumentsReceivedV2AttachmentBody(Collection<TagasisideType> receivedDocsInfos) { TagasisideArrayType receivedDocs = TagasisideArrayType.Factory.newInstance(); receivedDocs.setItemArray(receivedDocsInfos.toArray(new TagasisideType[receivedDocsInfos.size()])); if (log.isTraceEnabled()) { log.trace("created markDocumentsReceivedV2 attachmentBody: " + receivedDocs); } XmlOptions options = new XmlOptions(); options.setCharacterEncoding(DVK_MESSAGE_CHARSET); { // When marking documents received, with version 2 // they don't accept that item children, such as dhl_id and fault have nameSpace prefixes set HashMap<String, String> suggestedPrefixes = new HashMap<String, String>(2); suggestedPrefixes.put("http://www.riik.ee/schemas/dhl", ""); options.setSaveSuggestedPrefixes(suggestedPrefixes); options.setSaveNoXmlDecl(); } ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { receivedDocs.save(bos, options); String s = new String(bos.toByteArray(), DVK_MESSAGE_CHARSET); return gzipAndEncodeString(s); } catch (IOException e) { throw new RuntimeException("Failed to create markDocumentsReceivedV2 request attachmentBody", e); } } private XRoadAttachment setDokumendidHrefToAttachment(byte[] base64Attachment, XmlObject request) { final String cid = AttachmentUtil.getUniqueCid(); final XRoadAttachment attachment = new XRoadAttachment(cid, "{http://www.w3.org/2001/XMLSchema}base64Binary", base64Attachment); XmlCursor cursor = request.newCursor(); cursor.toNextToken(); Element node = (Element) cursor.getDomNode(); node.setAttribute("href", "cid:" + cid); cursor.dispose(); return attachment; } /** * @param <T> instance of this class will be returned if parsing <code>inputXml</code> is successful. * @param inputXml string representing xml * @param responseClass class of returnable instance * @return instance of given class T that extends XmlObject, parsed from <code>inputXml</code> */ public static <T extends XmlObject> T getTypeFromXml(String inputXml, Class<T> responseClass) { try { SchemaType sType = (SchemaType) responseClass.getField("type").get(null); if (log.isTraceEnabled()) { log.trace("Starting to parse '" + inputXml + "' to class: " + responseClass.getCanonicalName()); } XmlOptions replaceRootNameOpts = new XmlOptions().setLoadReplaceDocumentElement(new QName("xml-fragment")); final String xmlFragment = XmlObject.Factory.parse(inputXml, replaceRootNameOpts).toString(); @SuppressWarnings("unchecked") T result = (T) XmlObject.Factory.parse(xmlFragment, new XmlOptions().setDocumentType(sType)); return result; } catch (XmlException e) { throw new RuntimeException("Failed to parse '" + inputXml + "' to class: " + responseClass.getCanonicalName(), e); } catch (RuntimeException e) { throw e; } catch (Exception e) { // if above exceptions were not caught it must be because of bad class throw new IllegalArgumentException("Failed to get value of '" + responseClass.getCanonicalName() + ".type' to get corresponding SchemaType object: ", e); } } private static <T> List<T> getTypeFromGzippedAndEncodedSoapArray(InputStream inputStream, Class<T> responseClass) { SchemaType unencodedType = null; try { unencodedType = (SchemaType) responseClass.getField("type").get(null); log.debug("unencodedType=" + unencodedType); } catch (Exception e) { throw new RuntimeException("Failed to get value of '" + responseClass.getCanonicalName() + ".type' to get corresponding SchemaType object: ", e); } String responseString; try { responseString = new String(unzipAndDecode(inputStream), DVK_MESSAGE_CHARSET); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Failed to encode responseString to " + DVK_MESSAGE_CHARSET, e); } if (StringUtils.isBlank(responseString)) { return Collections.emptyList(); } ArrayList<T> result = null; try { responseString = "<root>" + responseString + "</root>"; XmlOptions options = new XmlOptions(); if (log.isTraceEnabled()) { log.trace("Starting to parse '" + responseString + "' to class: " + responseClass.getCanonicalName() + "\n\n"); } // XXX: potential optimization idea that could allow us to receive twice as big files as we can receive now: // use following line to create xmlObject, // but to avoid failure we must prepend to original inputStream some xml start element and append corresponding end element // but since rest of the inputStream is zipped and encoded to base64, then prepended and appended root element must also be zipped and encoded // XmlObject xmlObject = XmlObject.Factory.parse(unzip(decodeFromBase64(inputStream)), options); XmlObject xmlObject = XmlObject.Factory.parse(responseString, options); XmlCursor cursor = xmlObject.newCursor(); cursor.toFirstChild(); cursor.toFirstChild(); options.setDocumentType(unencodedType); result = new ArrayList<T>(); int i = 0; do { if (log.isTraceEnabled()) { cursor.getObject(); log.trace("Type of token " + (i++) + ": '" + cursor.currentTokenType() + "'"); } @SuppressWarnings("unchecked") T resultItem = (T) XmlObject.Factory.parse(cursor.getDomNode(), options); result.add(resultItem); } while (cursor.toNextSibling()); cursor.dispose(); } catch (XmlException e) { throw new RuntimeException("Failed to parse '" + responseString + "' to class: " + responseClass.getCanonicalName(), e); } return result; } private byte[] gzipAndEncodeXmlObject(XmlObject xmlObject) { OutputStream gzipBase64OutputStream = null; ByteArrayOutputStream gzippedAndEncodedOutputStream = new ByteArrayOutputStream(); Writer writer = null; try { writer = createOutputStreamWriter(gzippedAndEncodedOutputStream); gzipBase64OutputStream = createGzipBase64OutputStream(writer); sendDocumentsHelper.writeXmlObject(xmlObject, gzipBase64OutputStream); } catch (IOException e1) { throw new RuntimeException("Failed to encode input", e1); } finally { IOUtils.closeQuietly(gzipBase64OutputStream); IOUtils.closeQuietly(writer); } return gzippedAndEncodedOutputStream.toByteArray(); } private byte[] gzipAndEncodeString(String inputString) { OutputStream gzipBase64OutputStream = null; ByteArrayOutputStream gzippedAndEncodedOutputStream = new ByteArrayOutputStream(); Writer writer = null; try { writer = createOutputStreamWriter(gzippedAndEncodedOutputStream); gzipBase64OutputStream = createGzipBase64OutputStream(writer); IOUtils.write(inputString, gzipBase64OutputStream, DVK_MESSAGE_CHARSET); } catch (IOException e1) { throw new RuntimeException("Failed to encode input", e1); } finally { IOUtils.closeQuietly(gzipBase64OutputStream); IOUtils.closeQuietly(writer); } return gzippedAndEncodedOutputStream.toByteArray(); } private OutputStream createGzipBase64OutputStream( Writer writer) throws UnsupportedEncodingException, IOException { OutputStream base64OutputStream = Base64.newEncoder(writer, 0, null); OutputStream gzipOutputStream = new GZIPOutputStream(base64OutputStream); return gzipOutputStream; } private Writer createOutputStreamWriter(ByteArrayOutputStream gzippedAndEncodedOutputStream) throws UnsupportedEncodingException { return new BufferedWriter(new OutputStreamWriter(gzippedAndEncodedOutputStream, DVK_MESSAGE_CHARSET)); } /** * @param inputStream * @return plain content from transformed content: <code>base64Encode(gzip(content))</code> */ private static byte[] unzipAndDecode(InputStream inputStream) { return getUnzipAndDecodeOutputStream(inputStream, new ByteArrayOutputStream()).toByteArray(); } /** * @param <OS> OutputStream that is used as an output for <code>content</code> from the inputStream that was transformed * <code>base64Encode(gzip(content))</code> * @param attachments input to parse * @param responseClass destination class of the type to parse input * @param hasRootElement - if attachment bodies contain xml-fragments witout single root element set it to false so that root element would be created to be * able to parse document correctly * @return list of attachments parsed to given type */ private static <OS extends OutputStream> OS getUnzipAndDecodeOutputStream(InputStream inputStream, final OS outputStream) { final PipedOutputStream pipedOutputStream = new PipedOutputStream(); final List<Throwable> ungzipThreadThrowableList = new LinkedList<Throwable>(); Writer decoderWriter = null; Thread ungzipThread = null; try { final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream); ungzipThread = new Thread(new Runnable() { public void run() { GZIPInputStream gzipInputStream = null; try { // (3) pipedInputStream'i käsitletakse zip'itud voona, mida gzipInputStream unzip'ib gzipInputStream = new GZIPInputStream(pipedInputStream); // (4) gzipInputStream edastab temasse kirjutatud zip'itud andmed byteArrayOutputStream'i, unzippides IOUtils.copy(gzipInputStream, outputStream); } catch (Throwable t) { ungzipThreadThrowableList.add(t); } finally { IOUtils.closeQuietly(gzipInputStream); IOUtils.closeQuietly(pipedInputStream); } } }); decoderWriter = Base64.newDecoder(pipedOutputStream);// (2) decoderWriter edastab temasse kirjutatu pipedInputStream'i, eemaldades base64 kodeeringu ungzipThread.start(); IOUtils.copy(inputStream, decoderWriter, DVK_MESSAGE_CHARSET); // (1) - inputStream'ist base64 decoderWriter'isse kirjutamine, decoderWriter.flush(); pipedOutputStream.flush(); } catch (IOException e) { throw new RuntimeException("failed to unzip and decode input", e); } finally { IOUtils.closeQuietly(decoderWriter); IOUtils.closeQuietly(pipedOutputStream); if (ungzipThread != null) { try { ungzipThread.join(); } catch (InterruptedException ie) { throw new RuntimeException("thread interrupted while for ungzip thread to finish", ie); } } } if (!ungzipThreadThrowableList.isEmpty()) { throw new RuntimeException("ungzip failed", ungzipThreadThrowableList.get(0)); } return outputStream; } // START: getters/setters public GetDvkOrganizationsHelper getDvkOrganizationsHelper() { return dvkOrganizationsHelper; } public void setDvkOrganizationsHelper(GetDvkOrganizationsHelper dvkOrganizationsHelper) { this.dvkOrganizationsHelper = dvkOrganizationsHelper; } public void setReceivedDocumentsResponseRootElemName(String receivedDocumentsResponseRootElemName) { this.receivedDocumentsResponseRootElemName = receivedDocumentsResponseRootElemName; } public void setReceivedDocumentsFolder(String receivedDocumentsFolder) { this.receivedDocumentsFolder = receivedDocumentsFolder; } public void setSentDocumentsFolder(String sentDocumentsFolder) { this.sentDocumentsFolder = sentDocumentsFolder; } // END: getters/setters /** * Subclass of {@link InputStream} that will write everything read from wrapped inputStream to outputStream if outputStream is set - otherwise does nothing * more than delegates all method invocations to wrapped input stream. <br> * <br> * <b> NB! note that {@link #reset()} throws {@link UnsupportedOperationException} if outputStream is set and smth is already written there by this class * </b>(since write operation can't be taken back) * * @author Ats Uiboupin */ static class InputStreamWithOutputProxy extends ProxyInputStream { private OutputStream outputStream; private boolean smthWrittenToOut; public InputStreamWithOutputProxy(InputStream proxy) { super(proxy); } /** * @param outputStream - stream that will get written when proxied inputStream gets read */ public void setOutputStream(OutputStream outputStream) { this.outputStream = outputStream; } public OutputStream getOutputStream() { return outputStream; } @Override public void close() throws IOException { super.close(); if (outputStream != null) { outputStream.close(); } } @Override public int read() throws IOException { final int byteRead = super.read(); if (outputStream != null && byteRead != -1) { outputStream.write(byteRead); smthWrittenToOut = true; } return byteRead; } @Override public int read(byte[] bts, int st, int end) throws IOException { final int len = super.read(bts, st, end); if (outputStream != null && len != -1) { outputStream.write(bts, 0, len); smthWrittenToOut = true; } return len; } @Override public int read(byte[] bts) throws IOException { final int len = super.read(bts); if (outputStream != null && len != -1) { outputStream.write(bts, 0, len); // outputStream.write(bts); smthWrittenToOut = true; } return len; } /** * XXX Calling mark() is supported if wrapped inputstream supports it, <br> * however reseting is not supported if outputstream is set (and smth is * already written to ouput stream by this class because of read method) <br> * <i>Inherited documentation:</i> <br>{@inheritDoc} */ @Override public boolean markSupported() { return super.markSupported() && (outputStream == null || !smthWrittenToOut); } /** * <b>NB! note that this method throws {@link UnsupportedOperationException} if outputStream is set and smth is already written there by this class * </b>(since write operation can't be taken back),<br> * otherwise just resets wrapped inputStream as described bellow (in the documentation of superclass): <br> * <br> * <i>Inherited documentation:</i> <br>{@inheritDoc} */ @Override public synchronized void reset() throws IOException, UnsupportedOperationException { super.reset(); if (outputStream != null && smthWrittenToOut) { throw new UnsupportedOperationException("reseting inputstream is not supported w"); } } } }