Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Multiple File Upload Bug

    Hi, Isomorphic,
    I'm having some trouble when I try to insert a record that contains 2 images.

    Tried with both versions:
    v12.0p_2019-03-13/PowerEdition Deployment (built 2019-03-13)
    and
    v12.0p_2019-02-19/PowerEdition Deployment (built 2019-02-19)

    How to reproduce the problem:

    1) Images are sent to the server. I select a few images using smartgwt FileItem widget.
    2) On the server side (class AnexoDataSource), I get the image sent and resize it, making a thumbnail.
    3) The I save the original image and the resized image (using Isomorphic Framework / SQLDataSource).
    4) The thumbnail data is not saved in the database, a blank byte[] array is saved instead of the data.

    Here is my datasource:
    Code:
    <DataSource ID="atestado_anexo" serverType="sql" dataSourceVersion="1"
                serverConstructor="org.microcodigo.server.AnexoDataSource">
        <fields>
            <field name="codigo" type="sequence" title="Código" primaryKey="true" hidden="true">
            </field>
            <field name="cod_atestado" type="integer" required="true" hidden="true" title="Atestado">
            </field>
            <field name="title" title="Descrição">
            </field>
            <field name="image" type="imageFile" title="Imagem">
            </field>
            <field name="image_thumbnail" type="imageFile" hidden="true">
            </field>
        </fields>
        <allowAdvancedCriteria>true</allowAdvancedCriteria>
    
    </DataSource>
    My server side method:
    Code:
    package org.microcodigo.server;
    
    import com.isomorphic.datasource.DSRequest;
    import com.isomorphic.datasource.DSResponse;
    import com.isomorphic.servlet.ISCFileItem;
    import com.isomorphic.sql.SQLDataSource;
    import com.isomorphic.util.ErrorReport;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    import java.util.*;
    import javax.imageio.ImageIO;
    import org.apache.log4j.Level;
    import org.apache.log4j.Logger;
    
    /**
     *
     * @author kiipper
     */
    public class TestDataSource extends SQLDataSource {
    
        private static final float THUMBNAIL_SIZE = 200f;
    
        @Override
        public DSResponse executeAdd(final DSRequest dsRequest) throws Exception {
            DSResponse resp = null;
            byte[] imageData;
            String filename;
            if (dsRequest.getUploadedFiles() != null) {
                ArrayList<ISCFileItem> fileList = new ArrayList(dsRequest.getUploadedFiles());
                for (ISCFileItem fileItem : fileList) {
                    //final ISCFileItem fileItem = dsRequest.getUploadedFile("image");
                    imageData = fileItem.get();
                    filename = fileItem.getFileName(); //All file names come here in the same string. (ex. "filename1.png, filename2.png, filename3.png")
    
                    final Map<String, Object> imageValues = dsRequest.getValues();
                    int i = -1;
                    i = Math.max(i, filename.lastIndexOf('\\'));
                    i = Math.max(i, filename.lastIndexOf('/'));
                    filename = filename.substring(i + 1);
                    filename = filename.replaceAll("[^\\x00-\\x7F]", "");
                    imageValues.put("image_filename", filename);
                    imageValues.put("image", new ByteArrayInputStream(imageData));
                    imageValues.put("image_date_created", new Date());
                    imageValues.put("image_filesize", imageData.length);
    
                    try {
                        BufferedImage bImage = ImageIO.read(new ByteArrayInputStream(imageData));
                        if (bImage != null) {
                            int w = bImage.getWidth();
                            int h = bImage.getHeight();
                            float ratio = (float) THUMBNAIL_SIZE / (float) Math.max(w, h);
                            w = (int) (w * ratio);
                            h = (int) (h * ratio);
    
                            // Create a buffered image with transparency
                            Image img = bImage.getScaledInstance(w, h, Image.SCALE_SMOOTH);
                            BufferedImage image_thumbnail = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
                            // Draw the image on to the buffered image
                            Graphics2D bGr = image_thumbnail.createGraphics();
                            bGr.drawImage(img, 0, 0, null);
                            bGr.dispose();
    
                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            ImageIO.write(image_thumbnail.getSubimage(0, 0, w, h), "png", baos);
                            byte thumbData[];
                            thumbData = baos.toByteArray();
                            //thumbData = org.apache.commons.codec.binary.Base64.encodeBase64(baos.toByteArray());
    
                            imageValues.put("image_thumbnail", new ByteArrayInputStream(thumbData)); //You could use anything to test here, the problem persists (ex. new ByteArrayInputStream("some data".getBytes())
                            imageValues.put("image_thumbnail_filename", "tb_" + filename);
                            imageValues.put("image_thumbnail_date_created", new Date());
                            imageValues.put("image_thumbnail_filesize", thumbData.length);
    
                            dsRequest.setValues(imageValues);
    
                        }
                        resp = super.executeAdd(dsRequest);
                    } catch (NullPointerException | IOException ex) {
                        Logger.getLogger(this.getClass()).log(Level.ERROR, ex);
                        resp = new DSResponse();
                        ErrorReport er = new ErrorReport();
                        er.addError("image", ex.getMessage());
                        resp.setErrorReport(er);
                    }
                }
            }
            return resp;
        }
    }
    Thats my DB table 'PostgreSQL 9.6.9'
    Code:
    CREATE TABLE public.provisorio_anexo
    (
      codigo SERIAL NOT NULL PRIMARY KEY,
      title text,
      image bytea, -- Imagem, pdf, etc...
      image_date_created timestamp without time zone DEFAULT ('now'::text)::timestamp without time zone,
      image_filename text,
      image_filesize numeric,
      image_thumbnail bytea,
      image_thumbnail_date_created timestamp without time zone,
      image_thumbnail_filename text,
      image_thumbnail_filesize numeric
    )
    Side notes:
    1. It work when i upload only one image, but if I use more than one the problem happen.
    2. When I get the name of the image uploaded the return is an String with all files separated by ", ", this is not a problem since i can split then myself, but it might not be right.

    #2
    Hi guys,
    it's been a while and I'm still have the same problem.
    Do you have any feedback about this issue?
    Do you need more information?

    Comment


      #3
      We are working on this and will update this thread when we have news. Thank you for your patience.

      Comment


        #4
        Apologies for the late response.

        There's no need to subclass DataSource here and you are executing same request twice which is not recommended. Please consider using approach showed below.

        There's operationBinding bound to serverObject with custom logic for "add" operation with operationId="customAdd". You may use operationAdd setting to customize operation that will be called for upload.
        CustomDS is standalone class with customAdd microservice-like method. For every uploaded file it creates separate "add" request with no specific operationId, so it falls back to standard SQLDataSource behavior. Note how all responses are added as relatedUpdates to the main DSResponse, this will update caches for all data bound components on the client.
        There''s also an optional transaction, so that if some of requests fail, all changes would be rolled back. Remove that if you don't need this behavior.

        Code:
        <DataSource 
           ID="images"
           tableName="images"
           serverType="sql">
           <fields>
              <field name="id" type="sequence" primaryKey="true"/>
              <field name="title" type="text"/>
              <field name="image" type="imageFile"/>
              <field name="image_thumbnail" type="imageFile"/>
           </fields>
           <operationBindings>
              <operationBinding operationType="add" operationId="customAdd">
                 <serverObject className="com.isomorphic.test.CustomDS" lookupStyle="new" methodName="customAdd"/>
              </operationBinding>
           </operationBindings>
        </DataSource>
        Code:
        public class CustomDS {
        
            private static final float THUMBNAIL_SIZE = 200f;
        
            public DSResponse customAdd(DSRequest dsRequest) throws Exception {
                // start transaction
                DSTransaction tran = new DSTransaction();
                dsRequest.setDsTransaction(tran);
        
                DSResponse resp = new DSResponse();
                byte[] imageData;
                String filename;
                if (dsRequest.getUploadedFiles() != null) {
                    ArrayList<ISCFileItem> fileList = new ArrayList(dsRequest.getUploadedFiles());
                    for (int f = 0; f < fileList.size(); f++) {
                        ISCFileItem fileItem = fileList.get(f);
                        //final ISCFileItem fileItem = dsRequest.getUploadedFile("image");
                        imageData = fileItem.get();
                        filename = fileItem.getFileName(); //All file names come here in the same string. (ex. "filename1.png, filename2.png, filename3.png")
        
                        final Map<String, Object> imageValues = dsRequest.getValues();
                        int i = -1;
                        i = Math.max(i, filename.lastIndexOf('\\'));
                        i = Math.max(i, filename.lastIndexOf('/'));
                        filename = filename.substring(i + 1);
                        filename = filename.replaceAll("[^\\x00-\\x7F]", "");
                        filename = filename.split(",")[f].trim();
                        imageValues.put("image_filename", filename);
                        imageValues.put("image", new ByteArrayInputStream(imageData));
                        imageValues.put("image_date_created", new Date());
                        imageValues.put("image_filesize", imageData.length);
        
                        DSResponse relatedResponse = null;
                        try {
                            BufferedImage bImage = ImageIO.read(new ByteArrayInputStream(imageData));
                            if (bImage != null) {
                                int w = bImage.getWidth();
                                int h = bImage.getHeight();
                                float ratio = (float) THUMBNAIL_SIZE / (float) Math.max(w, h);
                                w = (int) (w * ratio);
                                h = (int) (h * ratio);
        
                                // Create a buffered image with transparency
                                Image img = bImage.getScaledInstance(w, h, Image.SCALE_SMOOTH);
                                BufferedImage image_thumbnail = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
                                // Draw the image on to the buffered image
                                Graphics2D bGr = image_thumbnail.createGraphics();
                                bGr.drawImage(img, 0, 0, null);
                                bGr.dispose();
        
                                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                                ImageIO.write(image_thumbnail.getSubimage(0, 0, w, h), "png", baos);
                                byte thumbData[];
                                thumbData = baos.toByteArray();
                                //thumbData = org.apache.commons.codec.binary.Base64.encodeBase64(baos.toByteArray());
        
                                imageValues.put("image_thumbnail", new ByteArrayInputStream(thumbData)); //You could use anything to test here, the problem persists (ex. new ByteArrayInputStream("some data".getBytes())
                                imageValues.put("image_thumbnail_filename", "tb_" + filename);
                                imageValues.put("image_thumbnail_date_created", new Date());
                                imageValues.put("image_thumbnail_filesize", thumbData.length);
                            }
        
                            // create and execute related request in the same transaction
                            DSRequest relatedRequest = new DSRequest(dsRequest.getDataSource(), "add");
                            relatedRequest.setDsTransaction(tran);
                            relatedRequest.setValues(imageValues);
                            relatedResponse = relatedRequest.execute();
        
                        } catch (NullPointerException | IOException ex) {
                            Logger.getLogger(this.getClass()).log(Level.ERROR, ex);
                            // create failed related response
                            relatedResponse = new DSResponse();
                            ErrorReport er = new ErrorReport();
                            er.addError("image", ex.getMessage());
                            relatedResponse.setErrorReport(er);
                        } finally {
                            // add related response to main response
                            resp.addRelatedUpdate(relatedResponse);
                        }
                    }
                }
        
                // commit or rollback transaction
                tran.complete();
        
                return resp;
            }
        
        }

        Comment


          #5
          Thank you, Isomorphic.
          It took some time... but it worked like a charm! :D

          The approach to call the method under the operationBinding is way better than extends SQLDataSource.
          We did not realized that it was calling twice.
          This could be a nice example (and best practice) to be included in future EE showcase.

          Comment

          Working...
          X