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