2011年12月10日土曜日

Google ドキュメントからインポート

以前作成したRAP版連絡先管理の宛名印刷処理では、クライアントでファイル選択を行うために、RAP対応の FileDialog ウィジェットを使用しましたが、この方法ではRCP版とRAP版で異なるコードが必要でした。また、VCard ファイルからデータをインポートする処理もRCPとRAPで、処理が異なります。

現在多くのクラウド・ストレージサービスが無料で利用できます。有料の場合でもかなり低コストです。また、PC上にデータを置くよりもクラウド・ストレージで管理した方が、仲間とデータを共有したり、モバイルデバイスからアクセスしたり、多くのメリットがあります。さらに、Google ドキュメントからインポートするように処理を変更すれば、RCPとRAPで同じコードが使えます。

ユーザーが Google ドキュメントの任意のフォルダを指定できるようにすべきでしょうが、簡単のために特定のフォルダ ( mycontact/vcard_import ) から VCard 一覧を取得してダイアログに表示し、選択したファイルをインポートすることにします。

Google Document API を使用するための準備
gdata-samples.java-1.46.0.zip をダウンロードして、その中から以下のファイルをプロジェクトの lib フォルダにコピーします。

gdata-client-1.0.jar
gdata-core-1.0.jar
gdata-docs-3.0.jar
google-collect-1.0-rc1.jar
jsr305.jar


さらに JavaMail API の mail.jar も lib フォルダにコピーしました。

これらの jar をplugin.xml のランタイムタブでクラスパスに追加します。

サンプルの sample.docs.DocumentList からインポート処理に必要なメソッドを抽出して、次のユーティリティクラスを作成します:
public class GoogleDocsClient {
    public DocsService service;
    ....

    public GoogleDocsClient(String applicationName) {

        service = new DocsService(applicationName);
        this.host = DEFAULT_HOST;
    }

    public boolean login(String user, String pass) {
        if (user == null || pass == null) {
            return false;
        }
        try {
            service.setUserCredentials(user, pass);
        } catch (AuthenticationException e) {
            return false;
        }
        return true;
    }

    public DocumentListFeed getDocsListFeed(String category)
            throws IOException, MalformedURLException, ServiceException,
            DocumentListException {
        if (category == null) {
            throw new DocumentListException("カテゴリが null");
        }

        URL url;

        if (category.equals("all")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED);
        } else if (category.equals("folders")) {
            String[] parameters = { PARAMETER_SHOW_FOLDERS };
            url = buildUrl(
                    URL_DEFAULT + URL_DOCLIST_FEED + URL_CATEGORY_FOLDER,
                    parameters);
        } else if (category.equals("documents")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED
                    + URL_CATEGORY_DOCUMENT);
        } else if (category.equals("spreadsheets")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED
                    + URL_CATEGORY_SPREADSHEET);
        } else if (category.equals("pdfs")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED + URL_CATEGORY_PDF);
        } else if (category.equals("presentations")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED
                    + URL_CATEGORY_PRESENTATION);
        } else if (category.equals("starred")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED
                    + URL_CATEGORY_STARRED);
        } else if (category.equals("trashed")) {
            url = buildUrl(URL_DEFAULT + URL_DOCLIST_FEED
                    + URL_CATEGORY_TRASHED);
        } else {
            return null;
        }

        return service.getFeed(url, DocumentListFeed.class);
    }

    public Map<String, String> getFolders(String folderName) {
        Map<String, String> docMap = new HashMap<String, String>();
        try {
            DocumentListFeed feed = getDocsListFeed("folders");
            for (DocumentListEntry doc : feed.getEntries()) {
                if (!doc.getParentLinks().isEmpty()) {
                    String parent = doc.getParentLinks().get(0).getTitle();
                    if (parent.equals(folderName)) {
                        docMap.put(doc.getTitle().getPlainText(),
                                doc.getResourceId());
                    }
                }
            }
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        } catch (ServiceException e) {
        } catch (DocumentListException e) {
        }
        return docMap;
    }

    public Map<String, String> getDocsMapByFolder(String folderId,
            String[] types) {
        Map<String, String> docMap = new HashMap<String, String>();
        try {
            DocumentListFeed feed = getFolderDocsListFeed(folderId);
            for (DocumentListEntry doc: feed.getEntries()) {
                String fileName = doc.getTitle().getPlainText();
                if (types!=null) {
                    for (String ext : types) {
                        if (fileName.endsWith(ext)) {
                            docMap.put(fileName, doc.getResourceId());
                            break;
                        }
                    }
                } else {
                    docMap.put(fileName, doc.getResourceId());
                }
            }
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        } catch (ServiceException e) {
        } catch (DocumentListException e) {
        }
        return docMap;
    }

    public Map<String, String> getDocsMap(String category) {
        Map<String, String> docMap = new HashMap<String, String>();
        try {
            DocumentListFeed feed = getDocsListFeed(category);
            for (DocumentListEntry doc : feed.getEntries()) {
                docMap.put(doc.getTitle().getPlainText(), doc.getResourceId());
            }
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        } catch (ServiceException e) {
        } catch (DocumentListException e) {
        }
        return docMap;
    }

    public InputStream getFileInputStream(String resourceId) {
        InputStream inStream = null;
        try {
            MediaContent mc = (MediaContent) getDocsListEntry(resourceId)
                    .getContent();
            MediaSource ms = service.getMedia(mc);
            inStream = ms.getInputStream();
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        } catch (ServiceException e) {
        } catch (DocumentListException e) {
        }
        return inStream;
    }
    ...
}



インポートハンドラの作成
上述の  GoogleDocsClient のコンストラクタでは、Google Docs サービスを生成しています。   
      service = new DocsService(applicationName);

アプリケーション名は2段階認証を行う場合に重要です。login()メソッドにより、Googleドキュメントにアクセスするための認証を行います。ここでは簡単にするために通常のクライアントログインを行っています。 getFolders() メソッドは指定されたフォルダ下の全フォルダのフォルダ名とリソースIDのマップを返します。getDocsMapByFolder() メソッドは指定されたフォルダ下の指定された拡張子を持つ全ファイルのファイル名とリソースIDのマップを返します。

これらのユーティリティメソッドを使って、google ドキュメントから VCard ファイルをインポートするハンドラ  ImportContacts.java を作成します:

public class ImportContact extends AbstractHandler implements IHandler {

    private static final String VCARD_IMPORT_FOLDER = "vcard_import";
    private String dir = "";

    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
        final Shell shell = PlatformUI.getWorkbench()
                .getActiveWorkbenchWindow().getShell();

        UserAcountHome uaHome = SessionSingletonManager.getInstance().getUserAcountHome();
        UserAcount ua = uaHome.getAcount();
        String pass = uaHome.getGooglepass();
        GoogleDocsClient docs = SessionSingletonManager.getInstance()
                .getGoogleDocsClient(ua.getId()+"@gmail.com", pass);
        if (docs == null)
            return null;

        Map<String, String> folders = SessionSingletonManager.getInstance().getDocsFolders();
        Map<String, String> docsMap = docs.getDocsMapByFolder(
                folders.get(VCARD_IMPORT_FOLDER), new String[]{".vcf"});
        boolean multiSelect = true;
        GDocsSelectDialog dlg = new GDocsSelectDialog(null,
                "インポートするVCardファイルを選択してください", "マイコンタクト", docsMap, multiSelect);
        if (dlg.open() == GDocsSelectDialog.CANCEL)
            return null;

        List<String> resourceIds = dlg.getSelectDocsIds();
        try {
            new ProgressMonitorDialog(shell).run(true, true,
                    new ImportOperation(docs, resourceIds, ua.getId()));



            // インポートされた連絡先で連絡先一覧の表示を更新
            ContactView view = (ContactView) PlatformUI.getWorkbench()
                    .getActiveWorkbenchWindow().getActivePage()
                    .findView(ContactView.ID);
            if (view != null) {
                view.getViewer().setInput(view.getViewSite());
            }

        } catch (InvocationTargetException e) {
            MessageDialog.openError(shell, "エラー:インポートできませんでした!!! ", e.getMessage());
        } catch (InterruptedException e) {
            MessageDialog.openInformation(shell, "キャンセルされました!!! ", e.getMessage());
        }
        return null;
    }

    private String getUserId() {
        return SessionSingletonManager.getInstance().getUserAcountHome()
                .getAcount().getId();
    }

    /***
     * インポート処理クラス
     */
    private class ImportOperation implements IRunnableWithProgress {
        private int fWorkCount;
        private boolean fIndeterminate;
        private List<String> fResourceIds;
        private GoogleDocsClient fDocs;
        private String fUserName;

        public ImportOperation(GoogleDocsClient docs, List<String> resourceIds,
                String userName) {
            fWorkCount = resourceIds.size();
            fResourceIds = resourceIds;
            fDocs = docs;
            fUserName = userName;
            fIndeterminate = false; // 今は使用しない
        }

        public void run(IProgressMonitor monitor)
                throws InvocationTargetException, InterruptedException {
            monitor.beginTask("選択された VCard をインポートしています...",
                    fIndeterminate ? IProgressMonitor.UNKNOWN : fWorkCount);
            ContactHome home = SessionSingletonManager.getInstance()
                    .getContactHome();

            try {
                for (String id : fResourceIds) {
                    if (monitor.isCanceled())
                        throw new InterruptedException("処理がキャンセルされました");

                    InputStream is = fDocs.getFileInputStream(id);
                    if (is != null) {
                        Contact c = VCardUtil.readFromVCard(is, fUserName);

                        // TODO 氏名と郵便番号または氏名と電話番号が等しい場合はインポートしない
                        monitor.subTask(c.getSimei());
                        monitor.worked(1);
                        // VCardUtil で住所インスタンス、電話番号インスタンスも設定済み
                        home.persist(c);
                        System.out.println(c.toString());
                    }
                }
                LocalJPA.commitAndClose();

            } catch (Exception e) {
                // TODO log
            } finally {
                LocalJPA.cleanup();
                monitor.done();
            }
                
        }
    }
}

上述のハンドラでは、GoogleDocsClient#getFileInputStream() により、指定されたリソースIDのドキュメントの InputStream を返します。この InputStream を使って、VCardUtil.readFromVCard() メソッドにより、Contact のインスタンスを取得します。VCardUtil.readFromVCard() メソッドについてはeclipselink を使う :(8)機能を追加するを参照してください。

ユーザーアカウントやデータベースアクセスに必要な ContactHome や Google Docs アクセスのためのユーティリティクラスは SessionSingletonManager から取得します。
SessionSingletoManagerは次のような内容です:

public class SessionSingletonManager extends SessionSingletonBase {

    private ContactHome fContactHome;
    private UserAcountHome fUserAcountHome;
    ...
    private GoogleDocsClient fGoogleDocs;
    private Map<String, String> fDocsFolders;


    private SessionSingletonManager() {
    }

    static public SessionSingletonManager getInstance() {
        return (SessionSingletonManager) getInstance(SessionSingletonManager.class);
    }

    public UserAcountHome getUserAcountHome() {
        if (fUserAcountHome == null) {
            fUserAcountHome = new UserAcountHome(UserAcount.class);
        }
        return fUserAcountHome;
    }

    public GoogleDocsClient getGoogleDocsClient(final String googleid,
            final String password) {
        if (fGoogleDocs == null) {
            fGoogleDocs = new GoogleDocsClient("mycontact");
            if (!fGoogleDocs.login(googleid, password)) {
                // 認証失敗の場合 null に設定
                fGoogleDocs = null;
            }
        }
        return fGoogleDocs;
    }

    public Map<String, String> getDocsFolders() {
        if (fDocsFolders == null) {
            if (fGoogleDocs != null) {
                fDocsFolders = fGoogleDocs.getFolders("mycontact");
            }
        }
        return fDocsFolders;
    }
    ...

}

Google ドキュメントの指定されたフォルダから VCard ファイル一覧を表示するダイアログは以下のようになります:
public class GDocsSelectDialog extends TitleAreaDialog {
    
    private class DocsContentProvider implements IStructuredContentProvider {
        public Object[] getElements(Object inputElement) {
            return (String[]) inputElement;
        }
        public void dispose() {
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }  
    }
    
    private class DocsSorter extends ViewerSorter {
        @Override
        public int compare(Viewer viewer, Object e1, Object e2) {
            return super.compare(viewer, e1, e2);
        }
    }
    
    private String fText;
    private String fTitle;

    private TableViewer fViewer;
    private Map<String, String> fDocsMap;
    
    private String fSelectDocsId;
    private List<String> fSelectDocsIds;
    private boolean fMulti;
    
    public GDocsSelectDialog(Shell parentShell, String text, String title,
                    Map<String, String> docsMap) {
        this(parentShell, text, title, docsMap, false);
    }
    
    public GDocsSelectDialog(Shell parentShell, String text, String title,
            Map<String, String> docsMap, boolean multi) {
        super(parentShell);
        fText = text;
        fTitle = title;
        fDocsMap = docsMap;
        fMulti = multi;
        fSelectDocsIds = new ArrayList<>();
    }
    
    @Override
    protected Control createDialogArea(Composite parent) {
        Composite area = (Composite) super.createDialogArea(parent);
        getShell().setText(fTitle);   //例:マイコンタクト
        setTitle(fText);        //例:用紙定義ファイルを選択してください
        area.setLayout(LayoutUtil.createGridLayout(1, false, 10, 5));
        
        if (fMulti) {
            fViewer = new TableViewer(area, SWT.H_SCROLL|SWT.V_SCROLL | SWT.MULTI);
        } else {
            fViewer = new TableViewer(area, SWT.H_SCROLL|SWT.V_SCROLL);
        }
        fViewer.setLabelProvider(new LabelProvider());
        fViewer.setContentProvider(new DocsContentProvider());
        fViewer.setSorter(new DocsSorter());
        fViewer.getControl().setLayoutData(LayoutUtil.createFillData());
        fViewer.setInput(fDocsMap.keySet().toArray(new String[]{}));
        
        return area;
    }
    
    @Override
    protected void okPressed() {
        
        StructuredSelection sel = (StructuredSelection)fViewer.getSelection();
        if (fMulti) {
            for (Object o: sel.toList()) {
                String s = (String)o;
                fSelectDocsIds.add(fDocsMap.get(s));
                System.out.println("key="+s+", val="+fDocsMap.get(s));
            }
            
        } else {
            String key = (String)sel.getFirstElement();
            fSelectDocsId = fDocsMap.get(key);
        }
        super.okPressed();
    }
    
    public String getSelectDocsId() {
        return fSelectDocsId;
    }
    
    public List<String> getSelectDocsIds() {
        return fSelectDocsIds;
    }

}

0 件のコメント: