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.POST;
009    import javax.ws.rs.Path;
010    import javax.ws.rs.Produces;
011    import javax.ws.rs.QueryParam;
012    import javax.ws.rs.WebApplicationException;
013    import javax.ws.rs.core.Context;
014    import javax.ws.rs.core.MediaType;
015    import javax.ws.rs.core.UriInfo;
016    
017    import de.deepamehta.core.osgi.PluginActivator;
018    import de.deepamehta.core.service.Inject;
019    import de.deepamehta.core.service.PluginService;
020    import de.deepamehta.core.service.ResultList;
021    import de.deepamehta.core.service.Transactional;
022    import de.deepamehta.plugins.files.DirectoryListing.FileItem;
023    import de.deepamehta.plugins.files.ItemKind;
024    import de.deepamehta.plugins.files.ResourceInfo;
025    import de.deepamehta.plugins.files.StoredFile;
026    import de.deepamehta.plugins.files.UploadedFile;
027    import de.deepamehta.plugins.files.service.FilesService;
028    import java.util.ArrayList;
029    
030    /**
031     * CKEditor compatible resources for image upload and browse.
032     */
033    @Path("/images")
034    public class ImagePlugin extends PluginActivator {
035    
036        public static final String IMAGES = "images";
037        
038        public static final String FILEREPO_BASE_URI_NAME = "filerepo";
039    
040        private static final String FILE_REPOSITORY_PATH = System.getProperty("dm4.filerepo.path");
041    
042        private static Logger log = Logger.getLogger(ImagePlugin.class.getName());
043    
044        @Inject
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        @Transactional
067        public String upload(//
068                UploadedFile image,//
069                @QueryParam("CKEditorFuncNum") Long func) {
070            log.info("upload image " + image.getName());
071            try {
072                StoredFile file = fileService.storeFile(image, IMAGES);
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        public void serviceArrived(PluginService service) {
120            if (service instanceof FilesService) {
121                log.fine("file service arrived");
122                fileService = (FilesService) service;
123                postInstallMigration();
124            }
125        }
126    
127        private void postInstallMigration() {
128            // TODO move the initialization to migration "0"
129            try {
130                // check image file repository
131                ResourceInfo resourceInfo = fileService.getResourceInfo(IMAGES);
132                if (resourceInfo.getItemKind() != ItemKind.DIRECTORY) {
133                    String message = "image storage directory " + FILE_REPOSITORY_PATH + File.separator
134                            + IMAGES + " can not be used";
135                    throw new IllegalStateException(message);
136                }
137            } catch (WebApplicationException e) {
138                // catch fileService info request error
139                if (e.getResponse().getStatus() != 404) {
140                    throw e;
141                } else {
142                    log.info("create image directory");
143                    fileService.createFolder(IMAGES, "/");
144                }
145            } catch (Exception e) {
146                throw new RuntimeException(e);
147            }
148        }
149    
150        /**
151         * Returns a in-line JavaScript snippet that calls the parent CKEditor.
152         * 
153         * @param func
154         *            CKEDITOR function number.
155         * @param uri
156         *            Resource URI.
157         * @param error
158         *            Error message.
159         * @return JavaScript snippet that calls CKEditor
160         */
161        private String getCkEditorCall(Long func, String uri, String error) {
162            return "<script type='text/javascript'>" + "window.parent.CKEDITOR.tools.callFunction("
163                    + func + ", '" + uri + "', '" + error + "')" + "</script>";
164        }
165    
166        /**
167         * Returns an external accessible file repository URI of path based on
168         * actual request URI.
169         * 
170         * @param path
171         *            Relative path of a file repository resource.
172         * @return URI
173         */
174        private String getRepoUri(String path) {
175            // ### in some cases the uriInfo (Context) may be empty!
176            return uriInfo.getBaseUri() + FILEREPO_BASE_URI_NAME + path;
177        }
178    }