]> git.argeo.org Git - lgpl/argeo-commons.git/blob - CmsContextImpl.java
ea9a401a4d3fc49ee2f617558432dbc11f170024
[lgpl/argeo-commons.git] / CmsContextImpl.java
1 package org.argeo.cms.internal.runtime;
2
3 import java.lang.management.ManagementFactory;
4 import java.util.ArrayList;
5 import java.util.HashMap;
6 import java.util.IdentityHashMap;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.Map;
10 import java.util.TreeMap;
11 import java.util.UUID;
12 import java.util.concurrent.CompletableFuture;
13 import java.util.concurrent.ExecutionException;
14 import java.util.concurrent.Flow;
15 import java.util.concurrent.Flow.Subscription;
16 import java.util.concurrent.SubmissionPublisher;
17
18 import javax.security.auth.Subject;
19
20 import org.argeo.api.cms.CmsContext;
21 import org.argeo.api.cms.CmsDeployment;
22 import org.argeo.api.cms.CmsEventSubscriber;
23 import org.argeo.api.cms.CmsLog;
24 import org.argeo.api.cms.CmsSession;
25 import org.argeo.api.cms.CmsSessionId;
26 import org.argeo.api.cms.CmsState;
27 import org.argeo.api.uuid.UuidFactory;
28 import org.argeo.cms.CmsDeployProperty;
29 import org.argeo.cms.internal.auth.CmsSessionImpl;
30 import org.ietf.jgss.GSSCredential;
31 import org.osgi.service.useradmin.UserAdmin;
32
33 public class CmsContextImpl implements CmsContext {
34
35 private final CmsLog log = CmsLog.getLog(getClass());
36 // private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
37
38 // private EgoRepository egoRepository;
39 private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
40
41 private CmsState cmsState;
42 private CmsDeployment cmsDeployment;
43 private UserAdmin userAdmin;
44 private UuidFactory uuidFactory;
45 // private ProvidedRepository contentRepository;
46
47 // i18n
48 private Locale defaultLocale;
49 private List<Locale> locales = null;
50
51 private Long availableSince;
52
53 // CMS sessions
54 private Map<UUID, CmsSessionImpl> cmsSessionsByUuid = new HashMap<>();
55 private Map<String, CmsSessionImpl> cmsSessionsByLocalId = new HashMap<>();
56
57 // CMS events
58 private Map<String, SubmissionPublisher<Map<String, Object>>> topics = new TreeMap<>();
59 // private IdentityHashMap<CmsEventSubscriber, List<CmsEventFlowSubscriber>> subscriptions = new IdentityHashMap<>();
60
61 // public CmsContextImpl() {
62 // initTrackers();
63 // }
64
65 public void start() {
66 List<String> codes = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.LOCALE);
67 locales = getLocaleList(codes);
68 if (locales.size() == 0)
69 throw new IllegalStateException("At least one locale must be set");
70 defaultLocale = locales.get(0);
71 // Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
72 // defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
73 // : new Locale(ENGLISH.getLanguage());
74 // node repository
75 // new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
76 // @Override
77 // public Repository addingService(ServiceReference<Repository> reference) {
78 // Object cn = reference.getProperty(NodeConstants.CN);
79 // if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
80 //// egoRepository = (EgoRepository) bc.getService(reference);
81 // if (log.isTraceEnabled())
82 // log.trace("Home repository is available");
83 // }
84 // return super.addingService(reference);
85 // }
86 //
87 // @Override
88 // public void removedService(ServiceReference<Repository> reference, Repository service) {
89 // super.removedService(reference, service);
90 //// egoRepository = null;
91 // }
92 //
93 // }.open();
94
95 checkReadiness();
96
97 setInstance(this);
98 }
99
100 public void stop() {
101 setInstance(null);
102 }
103
104 /**
105 * Checks whether the deployment is available according to expectations, and
106 * mark it as available.
107 */
108 private void checkReadiness() {
109 if (isAvailable())
110 return;
111 if (cmsDeployment != null && userAdmin != null) {
112 String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
113 String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
114 availableSince = System.currentTimeMillis();
115 long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
116 String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
117 log.info("## ARGEO CMS AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
118 if (log.isDebugEnabled()) {
119 log.debug("## state: " + state);
120 if (data != null)
121 log.debug("## data: " + data);
122 }
123 long begin = cmsState.getAvailableSince();
124 long initDuration = System.currentTimeMillis() - begin;
125 if (log.isTraceEnabled())
126 log.trace("Kernel initialization took " + initDuration + "ms");
127 tributeToFreeSoftware(initDuration);
128 } else {
129 throw new IllegalStateException("Deployment is not available");
130 }
131 }
132
133 final private void tributeToFreeSoftware(long initDuration) {
134 if (log.isTraceEnabled()) {
135 long ms = initDuration / 100;
136 log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
137 long beginNano = System.nanoTime();
138 try {
139 Thread.sleep(ms, 0);
140 } catch (InterruptedException e) {
141 // silent
142 }
143 long durationNano = System.nanoTime() - beginNano;
144 final double M = 1000d * 1000d;
145 double sleepAccuracy = ((double) durationNano) / (ms * M);
146 log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
147 }
148 }
149
150 @Override
151 public void createWorkgroup(String dn) {
152 // if (egoRepository == null)
153 // throw new CmsException("Ego repository is not available");
154 // // TODO add check that the group exists
155 // egoRepository.createWorkgroup(dn);
156 throw new UnsupportedOperationException();
157 }
158
159 /** Returns null if argument is null. */
160 private static List<Locale> getLocaleList(List<String> codes) {
161 if (codes == null)
162 return null;
163 ArrayList<Locale> availableLocales = new ArrayList<Locale>();
164 for (String code : codes) {
165 if (code == null)
166 continue;
167 // variant not supported
168 int indexUnd = code.indexOf("_");
169 Locale locale;
170 if (indexUnd > 0) {
171 String language = code.substring(0, indexUnd);
172 String country = code.substring(indexUnd + 1);
173 locale = new Locale(language, country);
174 } else {
175 locale = new Locale(code);
176 }
177 availableLocales.add(locale);
178 }
179 return availableLocales;
180 }
181
182 public void setCmsDeployment(CmsDeployment cmsDeployment) {
183 this.cmsDeployment = cmsDeployment;
184 }
185
186 public void setCmsState(CmsState cmsState) {
187 this.cmsState = cmsState;
188 }
189
190 public void setUserAdmin(UserAdmin userAdmin) {
191 this.userAdmin = userAdmin;
192 }
193
194 public UuidFactory getUuidFactory() {
195 return uuidFactory;
196 }
197
198 public void setUuidFactory(UuidFactory uuidFactory) {
199 this.uuidFactory = uuidFactory;
200 }
201
202 // public ProvidedRepository getContentRepository() {
203 // return contentRepository;
204 // }
205 //
206 // public void setContentRepository(ProvidedRepository contentRepository) {
207 // this.contentRepository = contentRepository;
208 // }
209
210 @Override
211 public Locale getDefaultLocale() {
212 return defaultLocale;
213 }
214
215 @Override
216 public List<Locale> getLocales() {
217 return locales;
218 }
219
220 @Override
221 public synchronized Long getAvailableSince() {
222 return availableSince;
223 }
224
225 public synchronized boolean isAvailable() {
226 return availableSince != null;
227 }
228
229 @Override
230 public CmsState getCmsState() {
231 return cmsState;
232 }
233
234 /*
235 * STATIC
236 */
237
238 public synchronized static CmsContextImpl getCmsContext() {
239 return getInstance();
240 }
241
242 // /** Required by USER login module. */
243 // public synchronized static UserAdmin getUserAdmin() {
244 // return getInstance().userAdmin;
245 // }
246
247 /** Required by SPNEGO login module. */
248 @Deprecated
249 public synchronized static GSSCredential getAcceptorCredentials() {
250 // FIXME find a cleaner way
251 return ((CmsUserAdmin) getInstance().userAdmin).getAcceptorCredentials();
252 }
253
254 private synchronized static void setInstance(CmsContextImpl cmsContextImpl) {
255 if (cmsContextImpl != null) {
256 if (instance.isDone())
257 throw new IllegalStateException("CMS Context is already set");
258 instance.complete(cmsContextImpl);
259 } else {
260 instance = new CompletableFuture<CmsContextImpl>();
261 }
262 }
263
264 private synchronized static CmsContextImpl getInstance() {
265 try {
266 return instance.get();
267 } catch (InterruptedException | ExecutionException e) {
268 throw new IllegalStateException("Cannot retrieve CMS Context", e);
269 }
270 }
271
272 public UserAdmin getUserAdmin() {
273 return userAdmin;
274 }
275
276 /*
277 * CMS Sessions
278 */
279
280 @Override
281 public synchronized CmsSession getCmsSession(Subject subject) {
282 if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
283 return null;
284 CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
285 return getCmsSessionByUuid(cmsSessionId.getUuid());
286 }
287
288 public synchronized void registerCmsSession(CmsSessionImpl cmsSession) {
289 if (cmsSessionsByUuid.containsKey(cmsSession.getUuid())
290 || cmsSessionsByLocalId.containsKey(cmsSession.getLocalId()))
291 throw new IllegalStateException("CMS session " + cmsSession + " is already registered.");
292 cmsSessionsByUuid.put(cmsSession.getUuid(), cmsSession);
293 cmsSessionsByLocalId.put(cmsSession.getLocalId(), cmsSession);
294 }
295
296 public synchronized void unregisterCmsSession(CmsSessionImpl cmsSession) {
297 if (!cmsSessionsByUuid.containsKey(cmsSession.getUuid())
298 || !cmsSessionsByLocalId.containsKey(cmsSession.getLocalId()))
299 throw new IllegalStateException("CMS session " + cmsSession + " is not registered.");
300 CmsSession removed = cmsSessionsByUuid.remove(cmsSession.getUuid());
301 assert removed == cmsSession;
302 cmsSessionsByLocalId.remove(cmsSession.getLocalId());
303 }
304
305 /**
306 * The {@link CmsSession} related to this UUID, or <code>null</null> if not
307 * registered.
308 */
309 public synchronized CmsSessionImpl getCmsSessionByUuid(UUID uuid) {
310 return cmsSessionsByUuid.get(uuid);
311 }
312
313 /**
314 * The {@link CmsSession} related to this local id, or <code>null</null> if not
315 * registered.
316 */
317 public synchronized CmsSessionImpl getCmsSessionByLocalId(String localId) {
318 return cmsSessionsByLocalId.get(localId);
319 }
320
321 /*
322 * CMS Events
323 */
324 public void sendEvent(String topic, Map<String, Object> event) {
325 SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
326 if (publisher == null)
327 return; // no one is interested
328 publisher.submit(event);
329 }
330
331 public void addEventSubscriber(String topic, CmsEventSubscriber subscriber) {
332 synchronized (topics) {
333 if (!topics.containsKey(topic))
334 topics.put(topic, new SubmissionPublisher<>());
335 }
336 SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
337 CmsEventFlowSubscriber flowSubscriber = new CmsEventFlowSubscriber(topic, subscriber);
338 publisher.subscribe(flowSubscriber);
339 }
340
341 public void removeEventSubscriber(String topic, CmsEventSubscriber subscriber) {
342 SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
343 if (publisher == null) {
344 log.error("There should be an event topic " + topic);
345 return;
346 }
347 for (Flow.Subscriber<? super Map<String, Object>> flowSubscriber : publisher.getSubscribers()) {
348 if (flowSubscriber instanceof CmsEventFlowSubscriber)
349 ((CmsEventFlowSubscriber) flowSubscriber).unsubscribe();
350 }
351 synchronized (topics) {
352 if (!publisher.hasSubscribers()) {
353 publisher.close();
354 topics.remove(topic);
355 }
356 }
357 }
358
359 static class CmsEventFlowSubscriber implements Flow.Subscriber<Map<String, Object>> {
360 private String topic;
361 private CmsEventSubscriber eventSubscriber;
362
363 private Subscription subscription;
364
365 public CmsEventFlowSubscriber(String topic, CmsEventSubscriber eventSubscriber) {
366 this.topic = topic;
367 this.eventSubscriber = eventSubscriber;
368 }
369
370 @Override
371 public void onSubscribe(Subscription subscription) {
372 this.subscription = subscription;
373 subscription.request(Long.MAX_VALUE);
374 }
375
376 @Override
377 public void onNext(Map<String, Object> item) {
378 eventSubscriber.onEvent(topic, item);
379
380 }
381
382 @Override
383 public void onError(Throwable throwable) {
384 // TODO Auto-generated method stub
385
386 }
387
388 @Override
389 public void onComplete() {
390 // TODO Auto-generated method stub
391
392 }
393
394 void unsubscribe() {
395 if (subscription != null)
396 subscription.cancel();
397 else
398 throw new IllegalStateException("No subscription to cancel");
399 }
400
401 }
402
403 }