воскресенье, 2 марта 2014 г.

Работа с файлами (af:inputFile,af:fileDownloadActionListener, af:image) в JDeveloper

     Редко какой ADF проект обходится без  работы с файлами.
     В этой статье мы создадим проект, в котором будет реализована возможность добавления в базу изображений(фотографию сотрудника), их просмотр  и скачивание на компьютер.
     Приступим.
     Будем использовать тестовую схему  Oracle (Scott\tiger). В ней есть таблица  EMP (сотрудники)
Для работы с файлами существующих таблиц  недостаточно. Нужно добавить поле IMAGE_ID  в таблицу EMP, создать таблицу PHOTO (с структурой указанной ниже) и связать эти таблицы связью 1 к 1 по полям  IMAGE_ID.





     Скрипт для создания таблицы PHOTO:
CREATE TABLE PHOTO( 
IMAGE_ID NUMBER(4, 0) NOT NULL
IMAGE_NAME VARCHAR2(250 BYTE)
TYPE VARCHAR2(50 BYTE)
DATA BLOB
CONSTRAINT PHOTO_PK PRIMARY KEY  (    IMAGE_ID  )  
ENABLE) ;

     Скрипт для добавления поля ID
ALTER TABLE EMP ADD (IMAGE_ID NUMBER(4) );

     Скрипт для создания связи между PHOTO и EMP  :
ALTER TABLE EMP
ADD CONSTRAINT EMP_PHOTO_FK1 FOREIGN KEY
(
  IMAGE_ID
)
REFERENCES PHOTO

(
  IMAGE_ID
)
ENABLE;

     Таблица PHOTO имеет 4 поля:
          - IMAGE_ID- ID фото
          - IMAGE_NAME-название файла
          - TYPE-тип файла в MIME формате.
          - DATA -поле с типом BLOB, в котором хранится сам файл.

     База подготовлена. Теперь приступим к созданию  самой  формы.

     Создайте  Fusion Web Application, connection к  базе и  на основе таблиц EMP и  Photo создайте Entitiy Object-ы(EO) , View Object-ы(VO)  и  View Link(VL) для связи созданных VO.
     Для вывода сотрудников  создайте jspx страницу (например empPhoto.jspx) и добавьте таблицу сотрудников  c VO связанной с фотографиями( в нашем случае EmpView2)


Добавьте следующие Binding's :

Из  итератора  фотографий связанной с сотрудниками (в нашем случае PhotoView2Iterator)
-ImageName;
-Type;
-Data;
-CreateInsert (Добавление записи в таблицу с фото).
  
Из  итератора  сотрудников связанной с фотографиями(в нашем случае EmpView2Iterator)
-ImageId;
-Empno.


     Так же добавьте операцию Commit для сохранения изменений в БД.
     Пример добавления Bindigs-ов показан ниже:



     В созданную таблицу добавьте колонку и вставьте туда кнопку, по нажатии которой будет выводиться popup с фотографией,кнопкой для скачивания  и добавления(обновления фото)
     В коде страницы  это будет выглядеть так:
<af:commandButton text="..." id="cb1" clientComponent="true">
  <af:showPopupBehavior triggerType="click" popupId=":p1" align="afterEnd"/>
   <af:setPropertyListener type="action" from="#{row.ImageId}"
                                 to="#{pageFlowScope.ifbean.ids}"/>
 </af:commandButton>

где af:commandButton -  кнопка
      af:showPopupBehavior - компонент для связывания кнопки и popup
      af:setPropertyListener - сохранение id  фото в bean-е.

Код  popup будет выглядеть так:
<af:popup childCreation="deferred" autoCancel="disabled" id="p1"
                                                 contentDelivery="immediate">
        <af:dialog id="d2" type="ok" dialogListener="#{pageFlowScope.ifbean.okClick}">
               <f:facet name="buttonBar"/>
               <af:image id="i1" source="/imageservlet?id=#{pageFlowScope.ifbean.ids}"
                                      inlineStyle="width:150px; height:150px;"/>
               <af:inputFile id="if1" valueChangeListener="#{pageFlowScope.ifbean.UploadListener}"/>
               <af:outputText value="#{bindings.ImageName.inputValue}" id="ot9"/>
               <af:commandButton text="Скачать" id="cb2">
                   <af:fileDownloadActionListener contentType="#{bindings.Type.inputValue}"
                                      filename="#{bindings.ImageName.inputValue}"
                                      method="#{pageFlowScope.ifbean.downloadImage}"/>
                </af:commandButton>
                        </af:dialog>
</af:popup>

Рассмотрим каждый компонент подробней:

1. af:image - вывод изображений по свойству source 
<af:image id="i1" source="/imageservlet?id=#{pageFlowScope.ifbean.ids}"
                                      inlineStyle="width:150px; height:150px;"/>
В Oracle ADF нет стандартных средств для вывода изображение из базы данных. И что бы изображение отображалась, нам нужно создать сервлет c url pattern /imageservlet  и в которых входящими данными будет id изображения. Код сервлета должен быть следующим :

package view;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;

import javax.servlet.*;
import javax.servlet.http.*;

import oracle.jbo.ApplicationModule;
import oracle.jbo.Row;
import oracle.jbo.client.Configuration;
import oracle.jbo.domain.BlobDomain;
import oracle.jbo.domain.Number;
import oracle.jbo.server.ViewObjectImpl;

public class ImageServlet
  extends HttpServlet
{
  private static final String CONTENT_TYPE = 
    "image/jpg; charset=windows-1252";
    @SuppressWarnings("compatibility:-2491699079303340020")
    private static final long serialVersionUID = 1L;

    public void init(ServletConfig config)
    throws ServletException
  {
    super.init(config);
  }

  public void doGet(HttpServletRequest request, 
                    HttpServletResponse response)
    throws ServletException, IOException {
          try {
    response.setContentType(CONTENT_TYPE);
    response.setContentType(CONTENT_TYPE);
    Number idPhoto=null;
   //условие по которому при пустом значении id, ему присваивается -1. Для вывода картинки "No photo" (Необходимо, что бы в таблице PHOTO было поле с id=-1 и изображением(например NO PHOTO))
   if (request.getParameter("id").equals(""))
   {
       idPhoto= new Number(-1);
   }else  idPhoto= new Number(request.getParameter("id"));
    OutputStream os = response.getOutputStream();
    String amDef = "model.AppModule"; // Адрес Application Module
    String config = "AppModuleLocal";// Выбор необходимой конфигурации в Application Module
    ApplicationModule am = 
      Configuration.createRootApplicationModule(amDef, config);
    ViewObjectImpl vo = 
      (ViewObjectImpl) am.findViewObject("PhotoView1");  // Поиск VO  в AppModule c фотографиями(VO который не связан с EmpView
    vo.defineNamedWhereClauseParam("paramIdPhoto", null, null);
    vo.setWhereClause("IMAGE_ID = :paramIdPhoto"); // создание условия по которому выбирается поле с необходимым нам фото
    vo.setNamedWhereClauseParam("paramIdPhoto", idPhoto);
    vo.executeQuery();
    Row product = vo.first();
    if (product!=null) {        
   BlobDomain image = (BlobDomain) product.getAttribute("Data"); 
    InputStream is = image.getInputStream();
    byte[] buffer = new byte[10 * 1024];
    int nread;
    while ((nread = is.read(buffer)) != -1)
      os.write(buffer, 0, nread);
        }
    os.close();
    vo.setWhereClause(null);
    vo.removeNamedWhereClauseParam("paramIdPhoto");
    Configuration.releaseRootApplicationModule(am, false);
      } catch (SQLException e) {
              System.out.println("SQLException"+e.getMessage());
        }
    }
}


2. af:inputFile - этот компонент используется для работы с файлами.Необходимо valueChangeListener и добавить в  туда следующий код:

    private BlobDomain data; 
    private String type;
    private String name;   

public void UploadListener(ValueChangeEvent valueChangeEvent) {
          // при добавлении файла срабатывает  valueChangeListener , в который передается  файл. Мы сохраняем файл, имя и  тип в созданные нами переменные, что бы потом добавить или обновить фотографию
          UploadedFile file = (UploadedFile) valueChangeEvent.getNewValue();
          name = file.getFilename();
          type = ContentTypes.get(name); // Создайте класс ContentTypes с кодом написанным ниже
          data=createBlobDomain(file);
    }
    private BlobDomain createBlobDomain(UploadedFile file)
    {
        InputStream in = null;
        BlobDomain blobDomain = null;
        OutputStream out = null;
        try
        {
            in = file.getInputStream();
            blobDomain = new BlobDomain();
            out = blobDomain.getBinaryOutputStream();
            IOUtils.copy(in, out);  // Эту библиотеку необходима скачать отсюда: http://commons.apache.org/proper/commons-io/download_io.cgi   и добавить в Libraries and Classpath проекта  
        }
      
        catch (SQLException e)
        {
            e.fillInStackTrace();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } 
        return blobDomain;
    }



   public class ContentTypes {
    public static String get(String fileName)
       {
           String mime = null;
           String ext = fileName.toLowerCase();

           if  (ext.endsWith(".jpg"))
           {
               mime = "image/jpeg";
           }
           else if (ext.endsWith(".jpeg"))
           {
               mime = "image/jpeg";
          }
           else if (ext.endsWith(".png"))
           {
               mime = "image/png";
           }
           return mime;
       }
}

3. <af:outputText value="#{bindings.ImageName.inputValue}" id="ot9"/> -  вывод имени файла
4. af:fileDownloadActionListener  - компонент добавляется для сохранения файла на ПК. Он имеет  три свойства:
contentType - тип содержимого в Mime формате (пр. image/jpeg);
filename - имя файла для сохранения;
method - метод для сохранения данных. Он имеет два аргумента: FacesContext и  OutputStream. Все данные записанные в  OutputStream сохраняются в файл с именем  filename  и типом contentType.

Ниже приведен код метода для сохранения фото при помощи  af:fileDownloadActionListener


      public void downloadImage(FacesContext facesContext, OutputStream outputStream)
    {
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("Data");
        if (attr == null)
        {
            return;
        }
        BlobDomain blob = (BlobDomain) attr.getInputValue();     
        try
        {  
            IOUtils.copy(blob.getInputStream(), outputStream);
            blob.closeInputStream();
            outputStream.flush();
        }
        catch (IOException e)
        {
            e.printStackTrace();
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), "");
            FacesContext.getCurrentInstance().addMessage(null, msg);
        }
    }

  Так же наш popup имеет компонент dialog с листенером на нажатие диалоговых кнопок

<af:dialog id="d2" type="ok" dialogListener="#{pageFlowScope.ifbean.okClick}">

Код ниже добавляет новое фото, если до этого изображение отсутствовало у сотрудника, и обновляет если изображение уже было.

      public void okClick(DialogEvent dialogEvent) {
        DCBindingContainer lBindingContainer =
            (DCBindingContainer) BindingContext.getCurrent().getCurrentBindingsEntry();
        DCIteratorBinding lBinding = lBindingContainer.findIteratorBinding("PhotoView2Iterator");
            AttributeBinding id = (AttributeBinding)lBindingContainer.getControlBinding("Empno");
            AttributeBinding attribute = (AttributeBinding) lBindingContainer.getControlBinding("ImageId");
            OperationBinding createPhoto=lBindingContainer.getOperationBinding("CreateInsert");
            OperationBinding commit=lBindingContainer.getOperationBinding("Commit");
        Row newRow = lBinding.getCurrentRow();
        if (newRow==null) {
            createPhoto.execute();
            newRow = lBinding.getCurrentRow();
            newRow.setAttribute("ImageId", id.getInputValue());                
            }
            newRow.setAttribute("ImageName", name);
            newRow.setAttribute("Data", data);
            newRow.setAttribute("Type", type);
            commit.execute();
            attribute.setInputValue(id);
            commit.execute();

        }

      Компонент af:form у jspx страницы обязательно должен иметь  свойство usesUpload="true", иначе af:inputFile не будет работать:


 <af:form id="f1" usesUpload="true">


Готово.