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