tohokuaikiのチラシの裏

技術的ネタとか。

Confluenceのプラグイン作成でファイル添付周りのコードを書く際に参考になりそうなところのメモ

AttachmentManagerかと思いきや、それを単純に使っただけではどうもよろしくない。

どうよろしくないかというと、

    public static void attachFile(AttachmentManager am, ContentEntityObject page, String temp_file_path, String fileName, String comment)
            throws FileNotFoundException, IOException, AttachmentDataExistsException {

        Attachment a;
        String mimetype;
        // String temp_file_path = 
        File temp_file = new File(temp_file_path);
        MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
        mimetype = mimetypesFileTypeMap.getContentType(temp_file);

        a = new Attachment(temp_file_path, mimetype, temp_file.length(), comment);
        a.setContent(page);
        a.setFileName(fileName);
        a.setVersion(1);
        am.saveAttachment(a, null, new FileInputStream(temp_file));
        am.setAttachmentData(a, new FileInputStream(temp_file));
        // throw new Exception("File ["+temp_file_path + "] not Found.("+ex.getLocalizedMessage()+")" );
    }

みたいなメソッドを作ったのだけど、どうもうまくいかない。
いや、ファイルはアップロードされて登録されるのだけど、
f:id:tohokuaiki:20140626140510p:plain
どうもページのこの数字が
f:id:tohokuaiki:20140626140505p:plain
増えない。

ということでソースコード検索

普通のアップロード方法のActionから追っていった。AttachmentManagerを直接つかうのではないっポイ。見つけたのが
confluence-core/confluence/src/java/com/atlassian/confluence/pages/DefaultFileUploadManager.java

    private void storeResources(List<StorableResource> storableResources, ContentEntityObject ceo)
    {
            List<SavableAttachment> saveableAttachments = new ArrayList<SavableAttachment>();
            for (StorableResource storableResource : storableResources)
            {
                Attachment attachment = attachmentManager.getAttachment(ceo, storableResource.filename);
                Attachment previousVersion = null;
        
                if (attachment == null)
                {
                    attachment = new Attachment();
                }
                else
                {
                    try
                    {
                        previousVersion = (Attachment) attachment.clone();
                    }
                    catch (CloneNotSupportedException e)
                    {
                        throw new InfrastructureException(e);
                    }
                }
        
        //        attachment.setContentType(StringUtils.isBlank(contentType) ? OCTET_STREAM_MIME_TYPE : contentType);
                attachment.setContentType(mimeTypeTranslator.resolveMimeType(storableResource.filename, storableResource.contentType));
        
                // if previous version exists, we want to preserve the case of the older version and disregard the new case of the newer version
                // if the user wants to change the case they can rename the attachment manually and allow the code invoked then to handle the required refactoring/renaming of links to this attachment
                if (previousVersion != null)
                    attachment.setFileName(previousVersion.getFileName());
                else
                    attachment.setFileName(storableResource.filename);
        
                attachment.setComment(storableResource.comment);
                attachment.setFileSize(storableResource.contentLength);
                ceo.addAttachment(attachment);
        
                InputStream resourceStream;
                try
                {
                    resourceStream = storableResource.resource.getInputStream();
                }
                catch (IOException e)
                {
                    throw new RuntimeException("Error opening input stream from resource: " + storableResource.resource, e);
                }
                saveableAttachments.add(new SavableAttachment(attachment, previousVersion, resourceStream));
            }
            
        try
        {
                attachmentManager.saveAttachments(saveableAttachments);
        }
        catch (IOException e)
        {
            throw new RuntimeException(e); 
        }
        finally
        {
                for (SavableAttachment attachment : saveableAttachments)
                {
                        IOUtils.closeQuietly(attachment.getAttachmentData());
                }
        }
    }

ポイントになるのは、

  1. attachmentManager.getAttachment(ceo, storableResource.filename);で前のバージョンを採ってきている。つまりファイル名でしか確認していない。

で、コンテントタイプ、ファイル名、コメント、ファイルサイズを設定して、

attachment.setContentType();
attachment.setFileName();
attachment.setComment();
attachment.setFileSize();
// ceo => ContentEntityObject(pageObjectでも)
ceo.addAttachment(attachment);

ContentEntityObjectでaddAttachmentしている。

このaddAttachmentで、ContentEntityObjectとAttachmentの双方にお互いのリンクポインタを設定している。実装は
confluence-core/confluence/src/java/com/atlassian/confluence/core/ContentEntityObject.java

    public void addAttachment(Attachment attachment)
    {
        getAttachments().add(attachment);
        attachment.setContent(this);
    }
SavableAttachment にいったんキャッシュ

いったんSavableAttachmentオブジェクトとして保管。このオブジェクトは保存時に必要な3つの引数を保存しているだけ。
confluence-core/confluence/src/java/com/atlassian/confluence/pages/SavableAttachment.java

AttachmentManager.saveAttachmentsが本丸

confluence-core/confluence/src/java/com/atlassian/confluence/pages/DefaultAttachmentManager.java

結局メインは、saveAttachmentsメソッド
attachmentManager.saveAttachmentはショートカット。
上記のコードは page.addAttachment(a) というのがないためにAttachmentにContentEntityObjectを結び付けられてなかった。

おそらくそれでこの辺りのコードがFireしてなかったのではないかな?

            //Finally, fire batch completed event
            eventManager.publishEvent(new AttachmentBatchUploadCompletedEvent(this, attachments));

余談:びっくりしたこと

ページ+添付ファイル名でAttachmentをユニークにしているのだけど、これはDBスキーマなどで保障されているわけではなく、AttachmentManager.saveAttachment()メソッドでもチェックしてるわけでもない。

なんと、適当にプログラムからsaveAttachmentしてしまうと添付ファイルができてしまう。うーん、Atlassian・・・・。