001package de.deepamehta.plugins.images;
002
003import de.deepamehta.core.osgi.PluginActivator;
004import de.deepamehta.core.service.Inject;
005import de.deepamehta.core.Topic;
006import de.deepamehta.core.service.ResultList;
007import de.deepamehta.core.service.Transactional;
008import de.deepamehta.plugins.files.DirectoryListing.FileItem;
009import de.deepamehta.plugins.files.*;
010
011import javax.ws.rs.*;
012import javax.ws.rs.core.Context;
013import javax.ws.rs.core.MediaType;
014import javax.ws.rs.core.UriInfo;
015import javax.ws.rs.ext.RuntimeDelegate;
016import java.io.File;
017import java.util.ArrayList;
018import java.util.logging.Logger;
019
020/**
021 * CKEditor compatible resources for image upload and browse.
022 */
023@Path("/images")
024public class ImagePlugin extends PluginActivator {
025    
026    private static Logger log = Logger.getLogger(ImagePlugin.class.getName());
027
028    public static final String FILEREPO_BASE_URI_NAME       = "filerepo";
029    public static final String FILEREPO_IMAGES_SUBFOLDER    = "images";
030    public static final String DM4_HOST_URL = System.getProperty("dm4.host.url");
031    // public static final String FILE_REPOSITORY_PATH = System.getProperty("dm4.filerepo.path");
032
033    @Inject
034    private FilesService fileService;
035
036    @Context
037    private UriInfo uriInfo;
038
039    /**
040     * CKEditor image upload integration, see
041     * CKEDITOR.config.filebrowserImageBrowseUrl
042     * 
043     * @param image
044     *            Uploaded file resource.
045     * @param func
046     *            CKEDITOR function number to call.
047     * @return JavaScript snippet that calls CKEditor
048     */
049    @POST
050    @Path("/upload/ckeditor")
051    @Consumes(MediaType.MULTIPART_FORM_DATA)
052    @Produces(MediaType.TEXT_HTML)
053    @Transactional
054    public String uploadCKEditor(UploadedFile image, @QueryParam("CKEditorFuncNum") Long func) {
055        log.info("upload image " + image.getName());
056        createImagesDirectoryInFileRepo();
057        try {
058            StoredFile file = fileService.storeFile(image, prefix() + File.separator + FILEREPO_IMAGES_SUBFOLDER);
059            String path = prefix() + File.separator + FILEREPO_IMAGES_SUBFOLDER + File.separator + file.getFileName();
060            return getCkEditorCall(func, getRepoUri(path), "");
061        } catch (Exception e) {
062            log.severe(e.getMessage() + ", caused by " + e.getCause().getMessage());
063            return getCkEditorCall(func, "", e.getMessage());
064        }
065    }
066
067    /**
068     * Standard image upload integration.
069     * @param image     Uploaded file resource.
070     * @return topic    File Topic
071     */
072    @POST
073    @Path("/upload")
074    @Consumes(MediaType.MULTIPART_FORM_DATA)
075    @Produces(MediaType.APPLICATION_JSON)
076    @Transactional
077    public Topic upload(UploadedFile image, @QueryParam("CKEditorFuncNum") Long func) {
078        log.info("upload image " + image.getName());
079        createImagesDirectoryInFileRepo();
080        try {
081            StoredFile file = fileService.storeFile(image, prefix() + File.separator + FILEREPO_IMAGES_SUBFOLDER);
082            return fileService.getFileTopic(prefix() + File.separator + FILEREPO_IMAGES_SUBFOLDER + File.separator + file.getFileName());
083        } catch (Exception e) {
084            log.severe(e.getMessage() + ", caused by " + e.getCause().getMessage());
085            return null;
086        }
087    }
088
089    /**
090     * Returns a set of all image source URLs.
091     * 
092     * @return all image sources
093     */
094    @GET
095    @Path("/browse")
096    @Produces(MediaType.APPLICATION_JSON)
097    public ResultList<Image> browse() {
098        log.info("browse images in repository path " + prefix() + File.separator + FILEREPO_IMAGES_SUBFOLDER);
099        createImagesDirectoryInFileRepo();
100        try {
101            ArrayList<Image> images = new ArrayList<Image>();
102            DirectoryListing imagesDirectory= fileService.getDirectoryListing(prefix() + File.separator +
103                    FILEREPO_IMAGES_SUBFOLDER);
104            for (FileItem image : imagesDirectory.getFileItems()) {
105                String src = getRepoUri(image.getPath());
106                images.add(new Image(src, image.getMediaType(), image.getSize(), image.getName()));
107            }
108            return new ResultList<Image>(images.size(), images);
109        } catch (WebApplicationException e) { // fileService.getDirectoryListing
110            throw e; // do not wrap it again
111        } catch (Exception e) {
112            throw new RuntimeException(e);
113        }
114    }
115
116    private void createImagesDirectoryInFileRepo() {
117        try {
118            // check image file repository
119            ResourceInfo resourceInfo = fileService.getResourceInfo(prefix() + File.separator +
120                    FILEREPO_IMAGES_SUBFOLDER);
121            // depending on prefix() we check for an "images" folder in the global or workspace filerepo
122            if (resourceInfo.getItemKind() != ItemKind.DIRECTORY) {
123                String message = "ImagePlugin: \"images\" storage directory in repo path " + fileService.getFile("/") +
124                        prefix() + File.separator + ImagePlugin.FILEREPO_IMAGES_SUBFOLDER + " can not be used";
125                throw new IllegalStateException(message);
126            }
127        } catch (WebApplicationException e) {
128            log.info("Created the \"images\" subfolder on the fly for new filerepo in " + fileService.getFile("/") +
129                    prefix() + File.separator + FILEREPO_IMAGES_SUBFOLDER + "!");
130            try {
131                fileService.createFolder(FILEREPO_IMAGES_SUBFOLDER, prefix());
132            } catch (RuntimeException ex) {
133                log.warning("RuntimeException caught during folder creation, presumably the folder " +
134                        "must already EXIST, so OK:" + ex.getMessage());
135            }
136            // catch fileService create request failed because of: folder exists
137        } catch (Exception e) {
138            throw new RuntimeException(e);
139        }
140    }
141
142    /**
143     * Returns a in-line JavaScript snippet that calls the parent CKEditor.
144     * 
145     * @param func
146     *            CKEDITOR function number.
147     * @param uri
148     *            Resource URI.
149     * @param error
150     *            Error message.
151     * @return JavaScript snippet that calls CKEditor
152     */
153    private String getCkEditorCall(Long func, String uri, String error) {
154        return "<script type='text/javascript'>" + "window.parent.CKEDITOR.tools.callFunction("
155                + func + ", '" + uri + "', '" + error + "')" + "</script>";
156    }
157
158    /**
159     * Returns an external accessible file repository URI of path based on the
160     * <code>dm4.host.url</code> platform configuration option.
161     * 
162     * @param path
163     *            Relative path of a file repository resource.
164     * @return URI
165     */
166    private String getRepoUri(String path) {
167        return DM4_HOST_URL + FILEREPO_BASE_URI_NAME + path;
168    }
169
170    private String prefix() {
171        File repo = fileService.getFile("/");
172        return ((FilesPlugin) fileService).repoPath(repo);
173    }
174
175}