1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.imaging;
35
36 import info.magnolia.context.SystemContext;
37 import info.magnolia.module.ModuleLifecycle;
38 import info.magnolia.module.ModuleLifecycleContext;
39 import info.magnolia.objectfactory.Components;
40 import info.magnolia.observation.WorkspaceEventListenerRegistration;
41 import info.magnolia.repository.RepositoryConstants;
42
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Iterator;
47 import java.util.LinkedHashMap;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.TreeSet;
51
52 import javax.imageio.ImageIO;
53 import javax.imageio.spi.IIORegistry;
54 import javax.imageio.spi.ServiceRegistry;
55 import javax.inject.Inject;
56 import javax.inject.Provider;
57 import javax.jcr.RepositoryException;
58 import javax.jcr.Session;
59 import javax.jcr.observation.Event;
60
61 import org.apache.commons.collections4.CollectionUtils;
62 import org.apache.commons.collections4.Transformer;
63 import org.apache.commons.lang3.StringUtils;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67
68
69
70 public class ImagingModule implements ModuleLifecycle {
71
72 private static final Logger log = LoggerFactory.getLogger(ImagingModule.class);
73
74 public static final String IMAGING = "imaging";
75
76 private static final Long GENERATOR_CACHE_FLUSH_DELAY = 1000L;
77
78 private static final Long GENERATOR_CACHE_FLUSH_MAX_DELAY = 5000L;
79
80 static final String GENERATORS_PATH = "/modules/imaging/config/generators";
81
82 private Provider<SystemContext> systemContextProvider;
83
84 private WorkspaceEventListenerRegistration.Handle eventHandle;
85
86 private Map<String, ImageGenerator> generators = new LinkedHashMap<String, ImageGenerator>();
87
88 private boolean storeGeneratedImages = true;
89
90 @Inject
91 public ImagingModule(Provider<SystemContext> systemContextProvider) {
92 this.systemContextProvider = systemContextProvider;
93 }
94
95
96
97
98 @Deprecated
99 public ImagingModule() {
100 this(() -> Components.getComponent(SystemContext.class));
101 }
102
103
104
105
106
107
108 @Override
109 public void start(ModuleLifecycleContext moduleLifecycleContext) {
110 ImageIO.scanForPlugins();
111
112
113 if (log.isDebugEnabled()) {
114 log.debug("This lists the formats currently available to the javax.imageio package, as installed on this system.");
115 log.debug("Supported input formats: {}", StringUtils.join(filter(ImageIO.getReaderFormatNames()), ", "));
116 log.debug("Supported input mime types: {}", StringUtils.join(filter(ImageIO.getReaderMIMETypes()), ", "));
117 log.debug("Supported output formats: {}", StringUtils.join(filter(ImageIO.getWriterFormatNames()), ", "));
118 log.debug("Supported output mime types: {}", StringUtils.join(filter(ImageIO.getWriterMIMETypes()), ", "));
119 }
120
121 if (moduleLifecycleContext.getPhase() == ModuleLifecycleContext.PHASE_SYSTEM_STARTUP) {
122 try {
123 eventHandle = WorkspaceEventListenerRegistration.observe(RepositoryConstants.CONFIG, GENERATORS_PATH, events -> {
124 while (events.hasNext()) {
125 Event event = events.nextEvent();
126 String generatorPath = null;
127 try {
128 generatorPath = StringUtils.substringAfter(event.getPath(), GENERATORS_PATH);
129 int idx = StringUtils.indexOf(generatorPath, "/", 1);
130 if (idx > 0) {
131 generatorPath = StringUtils.substring(generatorPath, 0, idx);
132 }
133 Session session = systemContextProvider.get().getJCRSession(IMAGING);
134 if (!generatorPath.startsWith("jcr:") && !generatorPath.startsWith("mgnl:") && session.nodeExists(generatorPath)) {
135 session.removeItem(generatorPath);
136 session.save();
137 }
138 } catch (RepositoryException e) {
139 log.warn("Unable to flush imaging cache for generator [{}].", generatorPath, e);
140 }
141 }
142 }).withDelay(GENERATOR_CACHE_FLUSH_DELAY, GENERATOR_CACHE_FLUSH_MAX_DELAY).withSubNodes(true).register();
143 } catch (RepositoryException e) {
144 log.warn("Unable to register image cache flush listener.", e);
145 }
146 }
147 }
148
149
150
151
152
153
154
155
156 @Override
157 public void stop(ModuleLifecycleContext moduleLifecycleContext) {
158
159
160 final IIORegistry registry = IIORegistry.getDefaultInstance();
161 final LocalFilter localFilter = new LocalFilter(Thread.currentThread().getContextClassLoader());
162
163 Iterator<Class<?>> categories = registry.getCategories();
164
165 while (categories.hasNext()) {
166 Class<?> category = categories.next();
167 Iterator<?> providers = registry.getServiceProviders(category, localFilter, false);
168
169
170 List<Object> providersCopy = new ArrayList<>();
171 while (providers.hasNext()) {
172 providersCopy.add(providers.next());
173 }
174
175 for (Object provider : providersCopy) {
176 registry.deregisterServiceProvider(provider);
177 log.debug("Unregistered locally installed provider class: {}", provider.getClass());
178 }
179 }
180
181 if (eventHandle != null && moduleLifecycleContext.getPhase() == ModuleLifecycleContext.PHASE_SYSTEM_SHUTDOWN) {
182 try {
183 eventHandle.unregister();
184 } catch (RepositoryException e) {
185 log.warn("Unable to unregister image cache flush listener.", e);
186 }
187 }
188 }
189
190 public void setGenerators(Map<String, ImageGenerator> generators) {
191 this.generators = generators;
192 }
193
194 public Map<String, ImageGenerator> getGenerators() {
195 return generators;
196 }
197
198 public boolean isStoreGeneratedImages() {
199 return storeGeneratedImages;
200 }
201
202 public void setStoreGeneratedImages(boolean storeGeneratedImages) {
203 this.storeGeneratedImages = storeGeneratedImages;
204 }
205
206
207
208
209 static class LocalFilter implements ServiceRegistry.Filter {
210 private final ClassLoader loader;
211
212 public LocalFilter(ClassLoader loader) {
213 this.loader = loader;
214 }
215
216 @Override
217 public boolean filter(Object provider) {
218 return provider.getClass().getClassLoader() == loader;
219 }
220 }
221
222
223
224
225 private Collection<String> filter(String... formats) {
226 final TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
227 set.addAll(Arrays.asList(formats));
228 CollectionUtils.transform(set, new Transformer() {
229 @Override
230 public Object transform(Object input) {
231 return ((String) input).toLowerCase();
232 }
233 });
234 return set;
235 }
236 }