Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,368,414
Community Members
 
Community Events
168
Community Groups

Accessing jira attachment file data from a jira plugin

I am writing a plugin which needs to access attached files and look into them, as well as create attachments.

So I have all the "managers" I need, I get an issue object, and I call issue.getAttachments() which returns com.atlassian.jira.issue.attachment.Attachment objects.

How do I get the actual file data? The Attachment object has the following getters:

getAuthor() : String

getCreated() : Timestamp

getFilename() : String (this one is the short filename, not the path, such as "image.jpg")

getFilesize() : Long

getGenericValue() : GenericValue (not deprecated, but probably should be soon)

getId() : Long

getIssue : GenericValue (deprecated)

getIssueObject : Issue

getMimetype() : String

getProperties : PropertySet

getGenericValue / getString / getLong (the GV accessors)

getTimestamp(String) : Timestamp

None of these let me get at the actual attachment data... Currently, I am doing something like this:

String attachmentBasePath = applicationProperties.getString(APKeys.JIRA_PATH_ATTACHMENTS);
String pkey = attachment.getIssueObject().getProjectObject().getKey();
String ikey = attachment.getIssueObject().getKey();

StringBuilder sb = new StringBuilder()
sb.append(attachmentBasePath);
sb.append(File.separator);
sb.append(pkey);
sb.append(File.separator);
sb.append(ikey);
sb.append(File.separator);
sp.append(attachment.getId()); // *

return sb.toString();

Furthermore, if you look at the * above, in jira 4.X you used to also have to append "_image.jpg" or whatever for the filename, this has apparently changed in jira 5.X. Clearly this isn't portable or safe and can break at any time JIRA changes its internals.

Even worse, I've determiend that APKeys.JIRA_PATH_ATTACHEMNTS stored in the DB does not get updated, so for example, if you use "create-home-zip" with the plugin SDK to save a database, then restore it from some other path later, all your tests/plugin will break because it can't find the attachments anymore. Production JIRAs probably don't change paths very often, but that makes this no less terrible. How do I do this? =(

Thanks!
-Carl

6 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

1 vote
Answer accepted

I found the canonical implementation I think.

/**                                                                                                                                                                                                                                                                                
     * Returns the physical File for the given Attachment.                                                                                                                                                                                                                             
     * This method performs better as it does not need to look up the issue object.                                                                                                                                                                                                    
     *                                                                                                                                                                                                                                                                                 
     * @param issue the issue the attachment belongs to.                                                                                                                                                                                                                               
     * @param attachment the attachment.                                                                                                                                                                                                                                               
     * @return the file.                                                                                                                                                                                                                                                               
     * @throws DataAccessException on failure getting required attachment info.                                                                                                                                                                                                        
     */                                                                                                                                                                                                                                                                                
    public static File getAttachmentFile(Issue issue, Attachment attachment) throws DataAccessException                                                                                                                                                                                
    {                                                                                                                                                                                                                                                                                  
        final File attachmentDir = getAttachmentDirectory(issue);                                                                                                                                                                                                                      
        return getAttachmentFile(AttachmentAdapter.fromAttachment(attachment), attachmentDir);                                                                                                                                                                                         
    }

This is in the AttachmentUtils class. I will use this and hopefully it won't break... but I'd still really like to see the atlassian dev's comments about the JIRA_PATH_ATTACHMENTS ApplicationProperty not being updated, as looking through the code I see it still uses that.

Another problem with this is, the AttachmentUtils method is static, meaning you can't write tests around it (if you call them outside of jira, you get an IllegalStateException sine the ComponentManager isn't initialized - this code should be moved to a non-static manager). I'd really like to have a jira dev weigh in, and I can file a jira issue if you like.

And, for Jira 6.2

String filePath = PathUtils.joinPaths(ComponentAccessor.getAttachmentPathManager().getDefaultAttachmentPath(), issue.getProjectObject().getKey(), issue.getKey(), attachment.getId().toString());
					File file = new File(filePath);
					if (file.exists()) {
						// attachments file exists...
					}

Since JIRA 6.3 there is also the bucket number in the middle of the complete path.
Using the FileAttachments class we could write:

bucketNumber = FileAttachments.computeIssueBucketDir(issue.getKey())
String filePath = PathUtils.joinPaths(ComponentAccessor.getAttachmentPathManager().getDefaultAttachmentPath(),
issue.getProjectObject().getKey(),
bucketNumber,
issue.getKey(),
attachment.getId().toString());

or better:

String attachmentPath = FileAttachments.getAttachmentDirectoryForIssue(new File(ComponentAccessor.getAttachmentPathManager().getDefaultAttachmentPath());
String filePath = PathUtils.joinPaths(attachmentPath,
issue.getProjectObject().getKey(),
issue.getKey()).toString(),
attachment.getId().toString());

in this last case you just need to know the project key , the issue key and the attachment id and you don't care about the bucket number

Like Andrea Pannitti likes this

Hi everyone, I know this thread is old but here is an example implementation of InputStreamConsumer.

Hope it helps!

 

public class FileInputStreamConsumer implements InputStreamConsumer<File>{
	
	private final String filename;
	public FileInputStreamConsumer(String filename) {
		this.filename = filename;
	}
	@Override
	public File withInputStream(InputStream is) throws IOException {
		final File f = File.createTempFile(FilenameUtils.getBaseName(filename), FilenameUtils.getExtension(filename));
		StreamUtils.copy(is, new FileOutputStream(f));
		return f;
	}
}

Then you can use it as you like to get files from attachement:

for (final Attachment a : attachs) {
				File f = null;
				try {
					f = am.streamAttachmentContent(a, new FileInputStreamConsumer(a.getFilename()));
				}
				catch (final IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if (f != null && f.exists()) {
					paths.add(f);
				}
			}

 

 

OMG! Thank you!!

Good job Olivier. It was really helpful.

If project key has been modified by an administrator, attachement path keep the old project key and the "joinPaths(pathManager.attachmentPath, issue.projectObject.key, issue.key, attachment.id.toString())" proposed by Jamie return a path with the new project key. So it does not work.

I do it with AttachmentUtils which return the correct path and File:

File attachmentFile = AttachmentUtils.getAttachmentFile(attachment);

Hello,

I've been using this way to retrieve the attatchment File object when needed. Maybe this method can help you too;

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.attachment.FileSystemAttachmentDirectoryAccessor
import com.atlassian.jira.issue.Issue

File getAttatchmentFile(Issue issue,String attatchmentId){
    return ComponentAccessor.getComponent(FileSystemAttachmentDirectoryAccessor.class).getAttachmentDirectory(issue).listFiles().find({
        File it->
         it.getName().equals(attatchmentId)
    });
}

Regards,

Italo Qualisoni

Trying to build a path to the attachment yourself is brittle, as JIRA can decide on a different directory structure (which seems to have happened with JIRA 7).

 

A better solution would be using AttachmentUtils.getAttachmentFile(attachment) as @christophe rousset suggested:

File attachmentFile = AttachmentUtils.getAttachmentFile(attachment);

 

However that method has been deprecated since 6.1. It still exists in 7.1, but if you just want to get the attachment content, it is suggested to use AttachmentManager.streamAttachmentContent(Attachment attachment, InputStreamConsumer<T> consumer) which is available since 6.1. Unfortunately, I haven't found a usage example and not sure how to construct  an InputStreamConsumer. Would appreciate an Atlassian dev jumping in.

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

TAGS

Atlassian Community Events