]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java
Introduce node initialisation.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / Kernel.java
1 package org.argeo.cms.internal.kernel;
2
3 import static bitronix.tm.TransactionManagerServices.getTransactionManager;
4 import static bitronix.tm.TransactionManagerServices.getTransactionSynchronizationRegistry;
5 import static java.util.Locale.ENGLISH;
6 import static org.apache.commons.io.FileUtils.copyDirectory;
7 import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
8 import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstanceDir;
9 import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstancePath;
10 import static org.argeo.jcr.ArgeoJcrConstants.ALIAS_NODE;
11 import static org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS;
12 import static org.argeo.util.LocaleChoice.asLocaleList;
13 import static org.osgi.framework.Constants.FRAMEWORK_UUID;
14
15 import java.io.File;
16 import java.io.IOException;
17 import java.lang.management.ManagementFactory;
18 import java.security.PrivilegedAction;
19 import java.util.HashMap;
20 import java.util.Hashtable;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Map;
24
25 import javax.jcr.Repository;
26 import javax.jcr.RepositoryFactory;
27 import javax.security.auth.Subject;
28 import javax.transaction.TransactionManager;
29 import javax.transaction.TransactionSynchronizationRegistry;
30 import javax.transaction.UserTransaction;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.jackrabbit.util.TransientFileFactory;
35 import org.argeo.ArgeoException;
36 import org.argeo.ArgeoLogger;
37 import org.argeo.cms.CmsException;
38 import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory;
39 import org.argeo.jcr.ArgeoJcrConstants;
40 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
41 import org.osgi.framework.BundleContext;
42 import org.osgi.framework.ServiceEvent;
43 import org.osgi.framework.ServiceListener;
44 import org.osgi.framework.ServiceReference;
45 import org.osgi.framework.ServiceRegistration;
46 import org.osgi.service.useradmin.UserAdmin;
47
48 import bitronix.tm.BitronixTransactionManager;
49 import bitronix.tm.BitronixTransactionSynchronizationRegistry;
50 import bitronix.tm.Configuration;
51 import bitronix.tm.TransactionManagerServices;
52
53 /**
54 * Argeo CMS Kernel. Responsible for :
55 * <ul>
56 * <li>security</li>
57 * <li>provisioning</li>
58 * <li>transaction</li>
59 * <li>logging</li>
60 * <li>local and remote file systems access</li>
61 * <li>OS access</li>
62 * </ul>
63 */
64 final class Kernel implements KernelHeader, KernelConstants, ServiceListener {
65 /*
66 * REGISTERED SERVICES
67 */
68 private ServiceRegistration<ArgeoLogger> loggerReg;
69 private ServiceRegistration<TransactionManager> tmReg;
70 private ServiceRegistration<UserTransaction> utReg;
71 private ServiceRegistration<TransactionSynchronizationRegistry> tsrReg;
72 private ServiceRegistration<Repository> repositoryReg;
73 private ServiceRegistration<RepositoryFactory> repositoryFactoryReg;
74 private ServiceRegistration<UserAdmin> userAdminReg;
75
76 /*
77 * SERVICES IMPLEMENTATIONS
78 */
79 private NodeLogger logger;
80 private BitronixTransactionManager transactionManager;
81 private BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry;
82 private OsgiJackrabbitRepositoryFactory repositoryFactory;
83 NodeRepository repository;
84 private NodeUserAdmin userAdmin;
85
86 // Members
87 private final static Log log = LogFactory.getLog(Kernel.class);
88 ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName());
89 private final BundleContext bc = Activator.getBundleContext();
90 private final NodeSecurity nodeSecurity;
91 private DataHttp dataHttp;
92 private KernelThread kernelThread;
93
94 private Locale defaultLocale = null;
95 private List<Locale> locales = null;
96
97 public Kernel() {
98 nodeSecurity = new NodeSecurity();
99 }
100
101 final void init() {
102 Subject.doAs(nodeSecurity.getKernelSubject(),
103 new PrivilegedAction<Void>() {
104 @Override
105 public Void run() {
106 doInit();
107 return null;
108 }
109 });
110 }
111
112 private void doInit() {
113 long begin = System.currentTimeMillis();
114
115 // Use CMS bundle classloader
116 ClassLoader currentContextCl = Thread.currentThread()
117 .getContextClassLoader();
118 Thread.currentThread().setContextClassLoader(
119 Kernel.class.getClassLoader());
120 try {
121 if (nodeSecurity.isFirstInit())
122 firstInit();
123
124 defaultLocale = new Locale(getFrameworkProp(I18N_DEFAULT_LOCALE,
125 ENGLISH.getLanguage()));
126 locales = asLocaleList(getFrameworkProp(I18N_LOCALES));
127 logger = new NodeLogger();
128
129 // Initialise services
130 initTransactionManager();
131 repository = new NodeRepository();
132 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
133 userAdmin = new NodeUserAdmin(transactionManager, repository);
134
135 // HTTP
136 ServiceReference<ExtendedHttpService> sr = bc
137 .getServiceReference(ExtendedHttpService.class);
138 if (sr != null)
139 addHttpService(sr);
140
141 // Kernel thread
142 kernelThread = new KernelThread(this);
143 kernelThread.setContextClassLoader(Kernel.class.getClassLoader());
144 kernelThread.start();
145
146 // Publish services to OSGi
147 publish();
148 } catch (Exception e) {
149 log.error("Cannot initialize Argeo CMS", e);
150 throw new ArgeoException("Cannot initialize", e);
151 } finally {
152 Thread.currentThread().setContextClassLoader(currentContextCl);
153 }
154
155 long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
156 log.info("## ARGEO CMS UP in " + (jvmUptime / 1000) + "."
157 + (jvmUptime % 1000) + "s ##");
158 long initDuration = System.currentTimeMillis() - begin;
159 if (log.isTraceEnabled())
160 log.trace("Kernel initialization took " + initDuration + "ms");
161 directorsCut(initDuration);
162 }
163
164 private void firstInit() {
165 log.info("## FIRST INIT ##");
166 File initDir = new File(getFrameworkProp(NODE_INIT,
167 KernelUtils.getOsgiInstancePath("../../../init")));
168 // TODO also uncompress archives
169 if (initDir.exists())
170 try {
171 copyDirectory(initDir, getOsgiInstanceDir());
172 log.info("CMS initialized from " + initDir.getCanonicalPath());
173 } catch (IOException e) {
174 throw new CmsException("Cannot initialize from " + initDir, e);
175 }
176 }
177
178 private void initTransactionManager() {
179 Configuration tmConf = TransactionManagerServices.getConfiguration();
180 tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID));
181
182 File tmBaseDir = new File(getFrameworkProp(TRANSACTIONS_HOME,
183 getOsgiInstancePath(DIR_TRANSACTIONS)));
184 File tmDir1 = new File(tmBaseDir, "btm1");
185 tmDir1.mkdirs();
186 tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog")
187 .getAbsolutePath());
188 File tmDir2 = new File(tmBaseDir, "btm2");
189 tmDir2.mkdirs();
190 tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog")
191 .getAbsolutePath());
192 transactionManager = getTransactionManager();
193 transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
194 }
195
196 private void publish() {
197 // Listen to service publication (also ours)
198 bc.addServiceListener(Kernel.this);
199
200 // Logging
201 loggerReg = bc.registerService(ArgeoLogger.class, logger, null);
202 // Transaction
203 tmReg = bc.registerService(TransactionManager.class,
204 transactionManager, null);
205 utReg = bc.registerService(UserTransaction.class, transactionManager,
206 null);
207 tsrReg = bc.registerService(TransactionSynchronizationRegistry.class,
208 transactionSynchronizationRegistry, null);
209 // User admin
210 userAdminReg = bc.registerService(UserAdmin.class, userAdmin,
211 userAdmin.currentState());
212 // JCR
213 Hashtable<String, String> regProps = new Hashtable<String, String>();
214 regProps.put(JCR_REPOSITORY_ALIAS, ALIAS_NODE);
215 repositoryReg = bc.registerService(Repository.class, repository,
216 regProps);
217 repositoryFactoryReg = bc.registerService(RepositoryFactory.class,
218 repositoryFactory, null);
219 }
220
221 void destroy() {
222 long begin = System.currentTimeMillis();
223 unpublish();
224
225 kernelThread.destroyAndJoin();
226
227 if (dataHttp != null)
228 dataHttp.destroy();
229 if (userAdmin != null)
230 userAdmin.destroy();
231 if (repository != null)
232 repository.destroy();
233 if (transactionManager != null)
234 transactionManager.shutdown();
235
236 bc.removeServiceListener(this);
237
238 // Clean hanging threads from Jackrabbit
239 TransientFileFactory.shutdown();
240
241 // Clean hanging Gogo shell thread
242 new GogoShellKiller().start();
243
244 nodeSecurity.destroy();
245 long duration = System.currentTimeMillis() - begin;
246 log.info("## ARGEO CMS DOWN in " + (duration / 1000) + "."
247 + (duration % 1000) + "s ##");
248 }
249
250 private void unpublish() {
251 userAdminReg.unregister();
252 repositoryFactoryReg.unregister();
253 repositoryReg.unregister();
254 tmReg.unregister();
255 utReg.unregister();
256 tsrReg.unregister();
257 loggerReg.unregister();
258 }
259
260 @Override
261 public void serviceChanged(ServiceEvent event) {
262 ServiceReference<?> sr = event.getServiceReference();
263 Object service = bc.getService(sr);
264 if (service instanceof Repository) {
265 Object jcrRepoAlias = sr
266 .getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
267 if (jcrRepoAlias != null) {// JCR repository
268 String alias = jcrRepoAlias.toString();
269 Repository repository = (Repository) bc.getService(sr);
270 Map<String, Object> props = new HashMap<String, Object>();
271 for (String key : sr.getPropertyKeys())
272 props.put(key, sr.getProperty(key));
273 if (ServiceEvent.REGISTERED == event.getType()) {
274 try {
275 repositoryFactory.register(repository, props);
276 dataHttp.registerRepositoryServlets(alias, repository);
277 } catch (Exception e) {
278 throw new CmsException(
279 "Could not publish JCR repository " + alias, e);
280 }
281 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
282 repositoryFactory.unregister(repository, props);
283 dataHttp.unregisterRepositoryServlets(alias);
284 }
285 }
286 } else if (service instanceof ExtendedHttpService) {
287 if (ServiceEvent.REGISTERED == event.getType()) {
288 addHttpService(sr);
289 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
290 dataHttp.destroy();
291 dataHttp = null;
292 }
293 }
294 }
295
296 private void addHttpService(ServiceReference<?> sr) {
297 // for (String key : sr.getPropertyKeys())
298 // log.debug(key + "=" + sr.getProperty(key));
299 ExtendedHttpService httpService = (ExtendedHttpService) bc
300 .getService(sr);
301 // TODO find constants
302 Object httpPort = sr.getProperty("http.port");
303 Object httpsPort = sr.getProperty("https.port");
304 dataHttp = new DataHttp(httpService, repository);
305 if (log.isDebugEnabled())
306 log.debug("HTTP " + httpPort
307 + (httpsPort != null ? " - HTTPS " + httpsPort : ""));
308 }
309
310 @Override
311 public Locale getDefaultLocale() {
312 return defaultLocale;
313 }
314
315 /** Can be null. */
316 @Override
317 public List<Locale> getLocales() {
318 return locales;
319 }
320
321 final private static void directorsCut(long initDuration) {
322 // final long ms = 128l + (long) (Math.random() * 128d);
323 long ms = initDuration / 100;
324 log.info("Spend " + ms + "ms"
325 + " reflecting on the progress brought to mankind"
326 + " by Free Software...");
327 long beginNano = System.nanoTime();
328 try {
329 Thread.sleep(ms, 0);
330 } catch (InterruptedException e) {
331 // silent
332 }
333 long durationNano = System.nanoTime() - beginNano;
334 final double M = 1000d * 1000d;
335 double sleepAccuracy = ((double) durationNano) / (ms * M);
336 if (log.isDebugEnabled())
337 log.debug("Sleep accuracy: "
338 + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100))
339 + " %");
340 }
341
342 /** Workaround for blocking Gogo shell by system shutdown. */
343 private class GogoShellKiller extends Thread {
344
345 public GogoShellKiller() {
346 super("Gogo shell killer");
347 setDaemon(true);
348 }
349
350 @Override
351 public void run() {
352 ThreadGroup rootTg = getRootThreadGroup(null);
353 Thread gogoShellThread = findGogoShellThread(rootTg);
354 if (gogoShellThread == null)
355 return;
356 while (getNonDaemonCount(rootTg) > 2) {
357 try {
358 Thread.sleep(100);
359 } catch (InterruptedException e) {
360 // silent
361 }
362 }
363 gogoShellThread = findGogoShellThread(rootTg);
364 if (gogoShellThread == null)
365 return;
366 System.exit(0);
367 }
368 }
369
370 private static ThreadGroup getRootThreadGroup(ThreadGroup tg) {
371 if (tg == null)
372 tg = Thread.currentThread().getThreadGroup();
373 if (tg.getParent() == null)
374 return tg;
375 else
376 return getRootThreadGroup(tg.getParent());
377 }
378
379 private static int getNonDaemonCount(ThreadGroup rootThreadGroup) {
380 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
381 rootThreadGroup.enumerate(threads);
382 int nonDameonCount = 0;
383 for (Thread t : threads)
384 if (!t.isDaemon())
385 nonDameonCount++;
386 return nonDameonCount;
387 }
388
389 private static Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
390 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
391 rootThreadGroup.enumerate(threads, true);
392 for (Thread thread : threads) {
393 if (thread.getName().equals("Gogo shell"))
394 return thread;
395 }
396 return null;
397 }
398
399 }