Skip to main content

When reading emails via IFS Connect Mail Reader each attachment in the mail is suppose to be read as separate application messages. But this only seems work when the attchements are only Text files such as .txt, .csv and .xml.

When sending binary files such as .pdf, .jpg, those files do not appear as a separate application message and therefore we cannot get hold of those files in order to attach them to a case.

Is this a limitation in Application 10 or am I missing some setting?

Below is the mail I sent with a txt and a jpg attachments.

 

Then in Application Messages queue there are 3 records created. One for the detail of the email  which is in html format. One for the email body. and one for the content of the .txt file. But no application message is created for the .jpg file.

 

Thanks and Cheers!

So I managed xto solve this by creating a custom mail reader in IFS connect. A mail reader is written with java and connected that as a new Connect reader. The java code is written in such a way that it reads the email and convert it into one json file that has all the attachments with its binary data converted to base64 string and saved them in separate json tags. This json file is the one that is attached to the applcaiton message then. Then I just created a routing rule that will read this Json file and using a PLSQLblock read a method that will convert the base64 string back into binary data and attach that file into DOCMAN.

So now when some customer send a mail to a given email it will automatically create a case in Call Center and attach all the .pdf files that are sent in that email. Will update more details about the correction soon.


Create a new custom connect reader following the steps in https://docs.ifs.com/techdocs/foundation1/050_development/024_integration/015_connect/060_transport_connector_development/020_connector_readers/#How%20to%20develop%20and%20deploy%20a%20Connector%20Reader

  1. First you must create the ‘ConnectConfigCustomDef.ins file which contains the necessary configurations for the new custom reader that you are developing. You can simply export the configurations of an existing mail reader and add those same configuration properties to the file that you are creating, BUT in addition add the ‘FACTORY_CLASS’ property.

The FACTORY_CLASSparameter has to be read-only, i.e. with write_protected_ => '1'. It should be the first parameter of your Reader definition, i.e. with ordinal_ => '101'. Define fully qualified class name of your factory class as parameters default value, e.g. default_value_ => 'ifs.fndint.connectsender.MyConnectSenderFactory'.

This factory class is one of the java files that you will create later in the step 3 below. Keep it as blank for now and fill it in once you know the java class name after step 3.

  1. You must create the folder structure and build.xml files according to how its explained in https://docs.ifs.com/techdocs/foundation1/050_development/024_integration/015_connect/060_transport_connector_development/project.htm

Refer the example source code given at the end of the above link.

Remember to create the 2 build.xml files (if you are following the same folder structure) as explained in the technical documentation. One in the Java project folder and the other in the Component\Source\Component folder which is referring the ant compiler to the build.xml in the java project folder.

I created a new connect reader in a new component called ‘CMOD’.

  1. Refer the source code of the existing mail reader which can be found in \fndbas\source\fndbas\framework\fndcn\connect_framework\src\ifs\fnd\connect\readers

Basically there are 3 java files that needs to be created

  1. CustomMailReaderConfig.java and 2. CustomMailReaderFactory.java – Create these just as its explained in the connect reader help in the link above.

3.   CustomMailReader.java – Create this as it is done in MailConnectReader.java in Fndbas. At first I just copied the same code as in \fndbas\source\fndbas\framework\fndcn\connect_framework\src\ifs\fnd\connect\readers\ MailConnectReader.java  and created an exact copy to make sure that it works correctly.

Once everything is compiling without issues you MUST run the ant compiler in the IFS build process In order to get this into IFS application and test it in the application. This is because the .class files that are generated must go into the ifsapp-int.ear file.

The framework has to have access to custom readers and senders located outside the IFS Connect JAR file, Therefore the custom readers and senders have to be (implemented as POJO classes) packed to JAR files available for the entire Enterprise Application, i.e. located in the lib folder of the actual EAR file (ifsapp-int.ear).

The EAR file is packed by the IFS Applications build process therefore the JAR files with custom readers and servers have to be copied to the server/dist/int/lib folder prior to packaging the EAR file.

This packaging is done by the build, by referring the attributes written in the build.xml file.

Once this is built you can modify the ConnectConfigCustomDef.ins with the correct value for the ‘FACTORY_CLASS’ and deploy that in the database.

Once that is done you can create a new Connector Reader by right clicking on Connector Readers > New

 

Then enabled this new mail reader and disable the existing one.

Send an email to the host email address and see if its working. Check in the integration server log files to see if there are any errors.

If everything is working fine then you can freely modify the mail reader logic that you have in your java file.

You have to modify the nativeLoop() method to match your requirement.

   @Override

   protected void nativeLoop() throws ReaderFailureException {

      try {

         if(log.debug) log.debug("Opening folder :&1]", folder.getFullName());

         folder.open(Folder.READ_WRITE);

         javax.mail.Messagef] msgs = folder.getMessages();

         if(log.debug) log.debug("#&1 mails are found.", msgs.length);

         for(javax.mail.Message msg : msgs) {

            JSONObject jobj = new JSONObject();

            JSONArray jAttchmentArray = new JSONArray();

            String subject = msg.getSubject();

            if(!selectMessage(subject)) {

               if(log.debug) log.debug("Subject k&1] dosn't match mask; skipping...", subject);

               continue;

            }

            jobj.put("Subject", subject);

            String sender = InternetAddress.toString(msg.getFrom());

            jobj.put("Sender", sender);

           :

            writePart("WholeEmail",msg,jobj,jAttchmentArray);

            jobj.put("Attachments", jAttchmentArray);

            processData(msgId, subject, new Content(jobj.toString()), "1");

           

            // Delete mail ??? Or should be moved to nativeDelete() and called from ReaderProcessor?

            msg.setFlag(Flags.Flag.DELETED, true);

            if(log.debug) log.debug("Mail marked for deletion");

         } // end message loop

      :

   }

 

   private void writePart(String ParentPartType, Part msgPart, JSONObject jobj, JSONArray jAttchmentArray) throws MessagingException, IOException, JSONException {

      Object msgContent = msgPart.getContent();

      if (msgPart.isMimeType("multipart/mixed")){

         Multipart part = (Multipart)msgContent;

         for(int i=0; i<part.getCount(); i++) {

            writePart("multipart/mixed",part.getBodyPart(i),jobj,jAttchmentArray);

         }

      }

      else if (msgPart.isMimeType("multipart/alternative")){

         Multipart part = (Multipart)msgContent;

         for(int i=0; i<part.getCount(); i++) {

            writePart("multipart/alternative",part.getBodyPart(i),jobj,jAttchmentArray);

         }

      }

      else if (msgPart.isMimeType("multipart/related")){
         :
      }

      else{
         if (disposition != null && (disposition.equals("ATTACHMENT") ||  disposition.equals("INLINE"))){
            JSONObject attachment = new JSONObject();
            JSONObject error = new JSONObject();
            String attachName = msgPart.getFileName();
            if (attachName == null ||  attachName.trim().isEmpty()) {
               attachName = "UnknownAttachmentName";
            }
            attachName = MimeUtility.decodeText(attachName);
            attachment.put("FileName", attachName);
            attachment.put("Disposition", disposition);
            Content data = getBodyPartContent(msgPart);
             if(data.bytes != null) {
                  attachment.put("FileContent", org.apache.commons.codec.binary.StringUtils.newStringUtf8(org.apache.commons.codec.binary.Base64.encodeBase64(data.bytes)));
                  attachment.put("DataType", "Binary");
               }
               else if(data.str != null) {
                  attachment.put("FileContent", (data.str));
                  attachment.put("DataType", "String");
               }
               jAttchmentArray.put(attachment);
               if(log.debug) log.debug("Attachment = &1", attachName);
            }
         }
         else{
            String msgBody = msgPart.getContent().toString();
            if (msgPart.isMimeType("text/plain")){
               jobj.put("BodyContentText", msgBody);
            }
            else if (msgPart.isMimeType("text/html")){
               jobj.put("BodyContentHtml", msgBody);
            }
            else{
               jobj.put("BodyContentUnknown", msgBody);
            }
         }
      }
 

   private void processData(String msgId, String subject, Content data, String part) throws ReaderFailureException {

      String partId = msgId + part;

      if(log.debug) log.debug("Creating Message with ID "&1] and Name '&2'", partId, subject);

      Message m = new Message(partId, subject);

      if(data.bytes!=null)

         m.setData(data.bytes, config.defEncoding);

      else

         m.setData(data.str);

      if(log.debug) log.debug("Processing message...");

      processMessage(m);

   }

 

   private Content getBodyPartContent(Part msgPart) throws MessagingException, IOException{
      if(log.debug){ log.debug("trying to read the attachment");
      log.debug(""&1]",msgPart.getFileName());
      }
      try{
         Object partContent = msgPart.getContent();
         if(log.debug) log.debug("Copied attachment to partContent");
         if(partContent instanceof String ) {
            if(log.debug) log.debug("Attachment is String");
            return new Content((String)partContent);
         }
         else
         {
            if(log.debug) log.debug("Attachment is Binary");
            InputStream is = msgPart.getInputStream();
            bytea] bytes = readStreamToArray(is);
            is.close();
            //byted] bytes = IOUtils.toByteArray(is);
            return new Content(bytes);
         }
      }
      catch(Exception e){
        :
   }
 

Here I have created org.json.JSONObject object and stores all the information about the email inside of it. It will has the following structure.

{"SentDate":"Fri Mar 22 13:18:12 CET 2024",
"Subject":"FW: Email Subject",
"Sender":"Maduranga <maduranga.@abcd.com>",
"BodyContentHtml":"<html xmlns:v=\"urn:schemas-microsoft-com:vml\" xml...",
"Attachments":I{"FileContent":"/9j/4AAQSkZJRgABAQAASABIAA.....D/",
"FileName":"Image.jpeg",
"Disposition":"INLINE",
"DataType":"Binary"},
{"FileContent":"/SDFGGTRGFDYTUU756675464..../",
"FileName":"doc.pdf",
"Disposition":"ATTACHMENT",
"DataType":"Binary"}]
}

Here each attachment is stored in an array where there is FileContent and FileName attributes.

So now  the Application message that gets created is going to have a .json file as an  Message Input Data. Now all you have to do is write a new RoutingAddress to process this data and extract the data from the .json file.

A plsql procedure is written to process this data and create a new Call Center Case depending on the sender email address, Subject and certain content in the email body.

The attachments are then inserted as Docman objects and attached to the Call center case.

PROCEDURE Attach_Files_To_Case___( case_id_      IN VARCHAR2,
                                   file_name_    IN VARCHAR2,
                                   file_content_ IN CLOB)
IS
      file_data_  BLOB;
      : 
BEGIN
   file_data_  := decode_base64___(file_content_);
   --Create Doc Title Rev
   doc_rev_    := Doc_Class_Default_API.Get_Default_Value_( doc_class_ ,'DocTitle','DOC_REV');
   doc_sheet_  := Doc_Class_Default_API.Get_Default_Value_( doc_class_ ,'DocTitle','DOC_SHEET');
   Client_SYS.Clear_Attr(attr_);
   Client_SYS.Clear_Attr(title_attr_);
   Client_SYS.Add_To_Attr('DOC_CLASS', doc_class_, attr_);
   Client_SYS.Add_To_Attr('DOC_SHEET', doc_sheet_, attr_);
   Client_SYS.Add_To_Attr('DOC_REV', doc_rev_, attr_);
   Client_SYS.Add_To_Attr('TITLE', file_name_, title_attr_);
   Client_SYS.Add_To_Attr('STRUCTURE', 0, title_attr_);
  
   Doc_Issue_API.Create_Title_And_Rev__ ( info_ , attr_ , title_attr_ );
   doc_no_ := Client_SYS.Get_Item_Value('DOC_NO', attr_); --get generated DOC_NO
  
   document_type_ := 'ORIGINAL';
   --Create file reference in IFS Docman
   Edm_File_API.Create_File_Reference(dummy_out_, doc_class_, doc_no_, doc_sheet_, dc_rev_, document_type_, file_type_, NULL, 1, user_file_name_);
  
   --Set file state in IFS Docman
   action_ := 'CheckOut';
   EDM_FILE_API.Set_File_State( doc_class_, doc_no_, doc_sheet_, doc_rev_, document_type_, ation_, NULL );
  
   --Start check in of local file in IFS Docman/
   action_ := 'StartCheckIn';
   EDM_FILE_API.Set_File_State( doc_class_, doc_no_, doc_sheet_, doc_rev_, document_type_, ation_, user_file_name_ );
  
   --Finish check in
   action_ := 'FinishCheckIn';
   EDM_FILE_API.Set_File_State( doc_class_, doc_no_, doc_sheet_, doc_rev_, document_type_, ation_, user_file_name_ );
  
   --Add View copy
   document_type_ := 'VIEW';
   EDM_FILE_API.Create_File_Reference(dummy_out_, doc_class_, doc_no_, doc_sheet_, doc_rev_, document_type_, file_type_, NULL, 0, user_file_name_);
   EDM_FILE_API.Set_File_State( doc_class_, doc_no_, doc_sheet_, doc_rev_, document_type_, action_, user_file_name_ );

    --Create file record in EDM
   Client_SYS.Clear_Attr(attr_);
   Client_SYS.Add_To_Attr('DOC_CLASS', doc_class_, attr_);
   Client_SYS.Add_To_Attr('DOC_NO', doc_no_, attr_);
   Client_SYS.Add_To_Attr('DOC_SHEET', doc_sheet_, attr_);
   Client_SYS.Add_To_Attr('DOC_REV', doc_rev_, attr_);
   Client_SYS.Add_To_Attr('DOC_TYPE', document_type_, attr_);
   action_ := 'DO';
   EDM_FILE_STORAGE_API.New__(info_,objid_,objversion_,attr_,action_);   
   operation_ := 'WRITE';
:
   EDM_FILE_STORAGE_API.Write_Blob_Data (objversion_,objid_ ,file_data_ );
  
   --Create doc reference
   ---KEYREF should be in format KEY_1^...KEY_N^
   Client_SYS.Clear_Attr(attr_);
   Client_SYS.Clear_Attr(info_);
   Client_SYS.Add_To_Attr('LU_NAME', 'CcCase', attr_);
   Client_SYS.Add_To_Attr('KEY_REF', 'CASE_ID=' || case_id_ ||'^', attr_);
   Client_SYS.Add_To_Attr('DOC_CLASS', doc_class_, attr_);
   Client_SYS.Add_To_Attr('DOC_NO', doc_no_, attr_);
   Client_SYS.Add_To_Attr('DOC_SHEET', doc_sheet_, attr_);
   Client_SYS.Add_To_Attr('KEEP_LAST_DOC_REV', 'Fixed', attr_);
   Client_SYS.Add_To_Attr('DOC_REV', doc_rev_, attr_);
   Client_SYS.Add_To_Attr('COPY_FLAG', 'OK', attr_);
   Client_SYS.Add_To_Attr('SURVEY_LOCKED_FLAG', 'Unlocked', attr_);
   action_ := 'DO';
   DOC_REFERENCE_OBJECT_API.NEW__( info_,objid_,objversion_,attr_,action_ );
END Attach_Files_To_Case___;

 


Reply