Do I save the file correctly?

I have an entity in which besides regular fields there is a field of type FileDescriptor:

@NamePattern("The document in the following state: %s|cardState")
@Table(name = "DEMO_CARD_ITEM")
@Entity(name = "demo$CardItem")
public class CardItem extends StandardEntity {
	private static final long serialVersionUID = -3956149009965224251L;

	@Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="ID")
    public UUID getId() { 
    	return id; 
    }
	
	@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CARD_TYPE_ID", nullable = false)
    protected CardType cardType;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CARD_SUB_TYPE_ID", nullable = false)
    protected CardSubType cardSubType;
    
    @Column(name = "CARD_STATE", nullable = false)
    protected String cardState = 
    	ResourceBundle.getBundle("MessagesBundle", new Locale("ru","RU")).getString("cardState");
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "CARD_CREATION_DATE", nullable = false)
    protected Date cardCreationDate = new Date();
    
    @Column(name = "CARD_OUTCOME_NUMBER", nullable = false)
    protected String cardOutcomeNumber;
    
    @Column(name = "CARD_THEME", nullable = false)
    protected String cardTheme;
    
    @Column(name = "CARD_NUMBER", nullable = true)
    protected String cardNumber;
    
    @Column(name = "CARD_AUTO_CREATION", nullable = true)
    protected Boolean cardAutoCreation;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "CARD_DATE", nullable = false)
    protected Date cardDate;

    @Column(name = "CARD_ORGANIZATION", nullable = false)
    protected String cardOrganization;

    @Column(name = "CARD_DELIVERY_METHOD", nullable = false)
    protected String cardDeliveryMethod;

    @Column(name = "CARD_ADDITIONAL_INFORMATION", nullable = true)
    protected String cardAdditionalInformation;
    
    @Column(name = "CARD_REGISTRATOR_NAME")
    protected String cardRegistratorName;
    
    @Column(name = "CARD_REGISTRATOR_NUMBER", nullable = false)
    protected String cardRegistratorNumber;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "CARD_REGISTRATION_DATE", nullable = false)
    protected Date cardRegistrationDate = new Date();
    
    @Column(name = "CARD_REGISTRATOR_PERFORMERS", nullable = false)
    protected String cardPerformers;
    
    @Column(name = "CARD_FOR_REVIEW", nullable = true)
    protected String cardForReview;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "IMAGE_FILE_ID")
    protected FileDescriptor imageFile;
    
	// SKIPPED
}

My card-item-edit.xml descriptor of the screen:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
        caption="msg://editCaption"
        class="com.client.web.item.CardItemEdit"
        datasource="cardItemDs"
        focusComponent="cardItemsGroup"
        lookupComponent="cardItemsGroup"
        messagesPack="com.client.web.item">
    
     <dsContext>
        <datasource id="cardItemDs" class="com.client.entity.CardItem" view="_local" allowCommit="false"/>
        	<collectionDatasource id="cardTypeDs" class="com.client.entity.CardType">
            <query>
                <![CDATA[select e from demo$CardType e]]>
            </query>
        </collectionDatasource>
        	<collectionDatasource id="cardSubTypeDs" class="com.client.entity.CardSubType">
            <query>
                <![CDATA[select s from demo$CardSubType s where s.cardType.id = :ds$cardTypeDs]]>
            </query>
        </collectionDatasource>
    </dsContext>
    
    <actions>
        <action id="save" caption="mainMsg://actions.Ok" invoke="save" />
        <action id="cancel" caption="mainMsg://actions.Cancel" invoke="cancel" />
    </actions>
    
    <dialogMode forceDialog="true" width="AUTO"/>
	<layout spacing="true">
		<hbox id="cardItemsGroup" spacing="true" margin="true">
			<vbox spacing="true" margin="true">
		        <fieldGroup id="fieldGroup1" datasource="cardItemDs">
		            <column width="500px">
		            	<field id="cardState" property="cardState" editable="false" datasource="cardItemDs" width="30%"/>
		                <field id="cardCreationDate" property="cardCreationDate" editable="false" datasource="cardItemDs" width="30%"/>
		                <field id="cardTypeField" caption="Π’ΠΈΠΏ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" width="30%">
		            		<lookupPickerField id="cardType" datasource="cardItemDs" property="cardType" optionsDatasource="cardTypeDs" width="30%"/>
						</field>
		                <field id="cardSubTypeField" caption="ΠŸΠΎΠ΄Ρ‚ΠΈΠΏ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°" width="30%">
		                	<lookupPickerField id="cardSubType" datasource="cardItemDs" property="cardSubType" optionsDatasource="cardSubTypeDs" width="30%"/>
						</field>
						<field id="cardTheme" datasource="cardItemDs" property="cardTheme" width="30%"/>
						<field id="cardNumber" datasource="cardItemDs" property="cardNumber" width="30%"/>
						<field id="cardAutoCreation" datasource="cardItemDs" property="cardAutoCreation" width="30%"/>
		                <field id="cardOutcomeNumber" datasource="cardItemDs" property="cardOutcomeNumber" width="30%"/>
		                <field id="cardDate" datasource="cardItemDs" property="cardDate" width="30%"/>
		                <field id="cardOrganization" datasource="cardItemDs" property="cardOrganization" width="30%"/>
		                <field id="cardDeliveryMethod" datasource="cardItemDs" property="cardDeliveryMethod" width="30%"/>
		                <field id="cardAdditionalInformation" datasource="cardItemDs" property="cardAdditionalInformation" width="30%"/>
		            </column>
		        </fieldGroup>
			</vbox>
			<vbox spacing="true" margin="true">
		        <fieldGroup id="fieldGroup2" datasource="cardItemDs">
		            <column width="500px">
		                <field id="cardRegistratorNumber" datasource="cardItemDs" property="cardRegistratorNumber" width="30%"/>
		                <field id="cardRegistrationDate" datasource="cardItemDs" property="cardRegistrationDate" width="30%"/>
		                <field id="cardPerformers" datasource="cardItemDs" property="cardPerformers" width="30%"/>
		                <field id="cardForReview" datasource="cardItemDs" property="cardForReview" width="30%"/>
		                <field id="cardRegistratorName" datasource="cardItemDs" property="cardRegistratorName" width="30%"/>
		            </column>
		        </fieldGroup>
			</vbox>
		</hbox>

        <groupBox caption="Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ (ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅)" spacing="true" height="250px" width="250px" expand="embeddedImage">
   			<embedded id="embeddedImage" width="100%" align="MIDDLE_CENTER"/>
   			<hbox align="BOTTOM_LEFT" spacing="true">
       			<upload id="uploadField"/>
       			<button id="clearImageBtn" caption="ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ" invoke="onClearImageBtnClick"/>
   			</hbox>
		</groupBox>

        
		<hbox id="actionsPane" spacing="true" visible="true">
			<button id="saveBtn" action="save"/>
			<button id="cancelBtn" action="cancel"/>
		</hbox>
    </layout>
</window>

My CardItemEdit controller of the screen:

public class CardItemEdit extends AbstractEditor<CardItem> {
	
    @Inject
    private Datasource<CardItem> cardItemDs;
	
	@Inject
    private LookupField cardType;
	
    @Inject
    private LookupField cardSubType;

    @Named("fieldGroup1.cardState")
    private Field cardState;

    @Named("fieldGroup1.cardCreationDate")
    private Field cardCreationDate;
    
    @Named("fieldGroup1.cardTheme")
    private Field cardTheme;
    
    @Named("fieldGroup1.cardNumber")
    private Field cardNumber;
    
    @Named("fieldGroup1.cardAutoCreation")
    private Field cardAutoCreation;

    @Named("fieldGroup1.cardOutcomeNumber")
    private Field cardOutcomeNumber;
		
    @Named("fieldGroup1.cardDate")
    private Field cardDate;
    
    @Named("fieldGroup1.cardOrganization")
    private Field cardOrganization;
    
    @Named("fieldGroup1.cardDeliveryMethod")
    private Field cardDeliveryMethod;

    @Named("fieldGroup1.cardAdditionalInformation")
    private Field cardAdditionalInformation;

    @Named("fieldGroup2.cardRegistratorNumber")
    private Field cardRegistratorNumber;

    @Named("fieldGroup2.cardRegistrationDate")
    private Field cardRegistrationDate;

    @Named("fieldGroup2.cardPerformers")
    private Field cardPerformers;
    
    @Named("fieldGroup2.cardForReview")
    private Field cardForReview;
    
    @Named("fieldGroup2.cardRegistratorName")
    private Field cardRegistratorName;
    
    @Inject
    private CardItemService cardItemService;
    
    @Inject
    private DataSupplier dataSupplier;
    
    @Inject
    private FileStorageService fileStorageService;
    
    @Inject
    private FileUploadingAPI fileUploadingAPI;
    
    @Inject
    private ExportDisplay exportDisplay;

    @Inject
    private Embedded embeddedImage;
    
    @Inject
    private FileUploadField uploadField;
    
    @Inject
    private Button downloadImageBtn;
    
    @Inject
    private Button clearImageBtn;    
    
    private static final int IMG_HEIGHT = 190;
    private static final int IMG_WIDTH = 220;   
    
    @Override
    public void init(Map<String, Object> params) {
        uploadField.addFileUploadSucceedListener(event -> {
            FileDescriptor fd = uploadField.getFileDescriptor();
            try {
                fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
            } catch (FileStorageException e) {
                throw new RuntimeException("Error saving file to FileStorage", e);
            }
            getItem().setImageFile(dataSupplier.commit(fd));
            displayImage();
        });

        uploadField.addFileUploadErrorListener(event ->
                showNotification("File upload error", NotificationType.HUMANIZED));

        cardItemDs.addItemPropertyChangeListener(event -> {
//            if ("imageFile".equals(event.getProperty()))
//                updateImageButtons(event.getValue() != null);
        });
    }
    
    @Override
    protected void postInit() {
        displayImage();
//        updateImageButtons(getItem().getImageFile() != null);
    }

    public void onDownloadImageBtnClick() {
        if (getItem().getImageFile() != null)
            exportDisplay.show(getItem().getImageFile(), ExportFormat.OCTET_STREAM);
    }

    public void onClearImageBtnClick() {
        getItem().setImageFile(null);
        displayImage();
    }

//    private void updateImageButtons(boolean enable) {
//        downloadImageBtn.setEnabled(enable);
//        clearImageBtn.setEnabled(enable);
//    }

    private void displayImage() {
        byte[] bytes = null;
        if (getItem().getImageFile() != null) {
            try {
                bytes = fileStorageService.loadFile(getItem().getImageFile());
            } catch (FileStorageException e) {
                
                showNotification("Unable to load image file", NotificationType.HUMANIZED);
            }
        }
        if (bytes != null) {
            embeddedImage.setSource(getItem().getImageFile().getName(), new ByteArrayInputStream(bytes));
            embeddedImage.setType(Embedded.Type.IMAGE);
            BufferedImage image;
            try {
                image = ImageIO.read(new ByteArrayInputStream(bytes));
                int width = image.getWidth();
                int height = image.getHeight();

                if (((double) height / (double) width) > ((double) IMG_HEIGHT / (double) IMG_WIDTH)) {
                    embeddedImage.setHeight(String.valueOf(IMG_HEIGHT));
                    embeddedImage.setWidth(String.valueOf(width * IMG_HEIGHT / height));
                } else {
                    embeddedImage.setWidth(String.valueOf(IMG_WIDTH));
                    embeddedImage.setHeight(String.valueOf(height * IMG_WIDTH / width));
                }
            } catch (IOException e) {
                
            }
            // refresh image
            embeddedImage.setVisible(false);
            embeddedImage.setVisible(true);
        } else {
            embeddedImage.setVisible(false);
        }
    }    
    
    public void save() {
    	CardItem cardItem = new CardItem();
    	
    	cardItem.setImageFile(uploadField.getFileDescriptor());
    	cardItem.setCardType(cardType.getValue());
    	cardItem.setCardSubType(cardSubType.getValue());
    	cardItem.setCardOutcomeNumber(cardOutcomeNumber.getValue());
    	cardItem.setCardOrganization(cardOrganization.getValue());
    	cardItem.setCardDeliveryMethod(cardDeliveryMethod.getValue());
    	cardItem.setCardDate(cardDate.getValue());
    	cardItem.setCardCreationDate(cardCreationDate.getValue());
    	cardItem.setCardAdditionalInformation(cardAdditionalInformation.getValue());
    	cardItem.setCardRegistratorName(cardRegistratorName.getValue());
    	cardItem.setCardTheme(cardTheme.getValue());
    	cardItem.setCardNumber(cardNumber.getValue());
    	cardItem.setCardAutoCreation(cardAutoCreation.getValue());
    	cardItem.setCardRegistratorNumber(cardRegistratorNumber.getValue());
    	cardItem.setCardRegistrationDate(cardRegistrationDate.getValue());
    	cardItem.setCardPerformers(cardPerformers.getValue());
    	cardItem.setCardForReview(cardForReview.getValue());
    	cardItem.setCardRegistratorName(cardRegistratorName.getValue());
    	
    	
    	cardItemService.saveCardItem(cardItem);
    	close(COMMIT_ACTION_ID);
    }
    
    public void cancel() {
    	close(CLOSE_ACTION_ID);
    }
}

My CardItemService:

@Service(CardItemService.NAME)
public class CardItemServiceBean implements CardItemService {

    @Inject
    private Persistence persistence;
	
	@Override
	public UUID saveCardItem(CardItem cardItem) {
        try(Transaction tx = persistence.createTransaction()) {
            persistence.getEntityManager().persist(cardItem);
            tx.commit();
        }
        return cardItem.getId();
	}
}

My views.xml:

<views xmlns="http://schemas.haulmont.com/cuba/view.xsd">

  	<view class="com.client.entity.CardItem" name="card-item-browse" extends="_local">
		<property name="cardType"/>
      	<property name="cardSubType"/>
  	</view>
  
	<view class="com.client.entity.CardItem" name="card-item-edit">
    	<property name="imageFile" view="_local">
    	</property>
	</view>
    
</views>

So, when I create a new document, I can attach, for example, an image:

img-01

I think it’s suspicious in the save method:

cardItem.setImageFile(uploadField.getFileDescriptor());

Do I save the files correctly?

How is the uploaded file stored? In the server file system or in the database?

I would be very grateful for the information. Thanks to all.

It is stored on app-core folder in your web server the filedescriptor is just an id that is link to your app-core folder.

1 Like