]> git.argeo.org Git - lgpl/argeo-commons.git/blob - Kernel.java
189dd08d769b14f602391c14961caa98308e07fa
[lgpl/argeo-commons.git] / Kernel.java
1 package org.argeo.cms.internal.kernel;
2
3 import java.lang.management.ManagementFactory;
4 import java.security.PrivilegedAction;
5 import java.util.HashMap;
6 import java.util.Map;
7
8 import javax.jcr.Repository;
9 import javax.jcr.RepositoryFactory;
10 import javax.security.auth.Subject;
11 import javax.transaction.TransactionManager;
12 import javax.transaction.TransactionSynchronizationRegistry;
13 import javax.transaction.UserTransaction;
14
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.apache.jackrabbit.util.TransientFileFactory;
18 import org.argeo.ArgeoException;
19 import org.argeo.cms.CmsException;
20 import org.argeo.cms.internal.transaction.SimpleTransactionManager;
21 import org.argeo.jackrabbit.OsgiJackrabbitRepositoryFactory;
22 import org.argeo.jcr.ArgeoJcrConstants;
23 import org.argeo.security.core.InternalAuthentication;
24 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
25 import org.osgi.framework.BundleContext;
26 import org.osgi.framework.ServiceEvent;
27 import org.osgi.framework.ServiceListener;
28 import org.osgi.framework.ServiceReference;
29 import org.osgi.util.tracker.ServiceTracker;
30 import org.springframework.security.core.context.SecurityContextHolder;
31
32 /**
33 * Argeo CMS Kernel. Responsible for :
34 * <ul>
35 * <li>security</li>
36 * <li>provisioning</li>
37 * <li>transaction</li>
38 * <li>logging</li>
39 * <li>local and remote file systems access</li>
40 * <li>OS access</li>
41 * </ul>
42 */
43 final class Kernel implements ServiceListener {
44 private final static Log log = LogFactory.getLog(Kernel.class);
45
46 private final BundleContext bundleContext = Activator.getBundleContext();
47 private final NodeSecurity nodeSecurity;
48
49 ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName());
50 JackrabbitNode node;
51
52 private SimpleTransactionManager transactionManager;
53 private OsgiJackrabbitRepositoryFactory repositoryFactory;
54 private NodeHttp nodeHttp;
55 private KernelThread kernelThread;
56
57 public Kernel() {
58 nodeSecurity = new NodeSecurity(bundleContext);
59 }
60
61 final void init() {
62 Subject.doAs(nodeSecurity.getKernelSubject(),
63 new PrivilegedAction<Void>() {
64
65 @Override
66 public Void run() {
67 doInit();
68 return null;
69 }
70
71 });
72 }
73
74 private void doInit() {
75 ClassLoader currentContextCl = Thread.currentThread()
76 .getContextClassLoader();
77 Thread.currentThread().setContextClassLoader(
78 Kernel.class.getClassLoader());
79 long begin = System.currentTimeMillis();
80 InternalAuthentication initAuth = new InternalAuthentication(
81 KernelConstants.DEFAULT_SECURITY_KEY);
82 SecurityContextHolder.getContext().setAuthentication(initAuth);
83
84 try {
85 // Transaction
86 transactionManager = new SimpleTransactionManager();
87 bundleContext.registerService(TransactionManager.class,
88 transactionManager, null);
89 bundleContext.registerService(UserTransaction.class,
90 transactionManager, null);
91 bundleContext.registerService(
92 TransactionSynchronizationRegistry.class,
93 transactionManager.getTransactionSynchronizationRegistry(),
94 null);
95
96 // Jackrabbit node
97 node = new JackrabbitNode(bundleContext);
98
99 // JCR repository factory
100 repositoryFactory = new OsgiJackrabbitRepositoryFactory();
101
102 // Authentication
103 nodeSecurity.getUserAdmin().setTransactionManager(
104 transactionManager);
105
106 // Equinox dependency
107 ExtendedHttpService httpService = waitForHttpService();
108 nodeHttp = new NodeHttp(httpService, node, nodeSecurity);
109
110 // Kernel thread
111 kernelThread = new KernelThread(this);
112 kernelThread.setContextClassLoader(Kernel.class.getClassLoader());
113 kernelThread.start();
114
115 // Publish services to OSGi
116 nodeSecurity.publish();
117 node.publish(repositoryFactory);
118 bundleContext.registerService(RepositoryFactory.class,
119 repositoryFactory, null);
120
121 bundleContext.addServiceListener(Kernel.this);
122 } catch (Exception e) {
123 log.error("Cannot initialize Argeo CMS", e);
124 throw new ArgeoException("Cannot initialize", e);
125 } finally {
126 Thread.currentThread().setContextClassLoader(currentContextCl);
127 }
128
129 long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
130 log.info("## ARGEO CMS UP in " + (jvmUptime / 1000) + "."
131 + (jvmUptime % 1000) + "s ##");
132 long initDuration = System.currentTimeMillis() - begin;
133 if (log.isTraceEnabled())
134 log.trace("Kernel initialization took " + initDuration + "ms");
135 directorsCut(initDuration);
136 }
137
138 void destroy() {
139 long begin = System.currentTimeMillis();
140
141 kernelThread.destroyAndJoin();
142
143 if (nodeHttp != null)
144 nodeHttp.destroy();
145 // if (nodeSecurity != null)
146 // nodeSecurity.destroy();
147 if (node != null)
148 node.destroy();
149
150 bundleContext.removeServiceListener(this);
151
152 // Clean hanging threads from Jackrabbit
153 TransientFileFactory.shutdown();
154
155 // Clean hanging Gogo shell thread
156 new GogoShellKiller().start();
157
158 nodeSecurity.destroy();
159 long duration = System.currentTimeMillis() - begin;
160 log.info("## ARGEO CMS DOWN in " + (duration / 1000) + "."
161 + (duration % 1000) + "s ##");
162 }
163
164 @Override
165 public void serviceChanged(ServiceEvent event) {
166 ServiceReference<?> sr = event.getServiceReference();
167 Object jcrRepoAlias = sr
168 .getProperty(ArgeoJcrConstants.JCR_REPOSITORY_ALIAS);
169 if (jcrRepoAlias != null) {// JCR repository
170 String alias = jcrRepoAlias.toString();
171 Repository repository = (Repository) bundleContext.getService(sr);
172 Map<String, Object> props = new HashMap<String, Object>();
173 for (String key : sr.getPropertyKeys())
174 props.put(key, sr.getProperty(key));
175 if (ServiceEvent.REGISTERED == event.getType()) {
176 try {
177 repositoryFactory.register(repository, props);
178 nodeHttp.registerRepositoryServlets(alias, repository);
179 } catch (Exception e) {
180 throw new CmsException("Could not publish JCR repository "
181 + alias, e);
182 }
183 } else if (ServiceEvent.UNREGISTERING == event.getType()) {
184 repositoryFactory.unregister(repository, props);
185 nodeHttp.unregisterRepositoryServlets(alias);
186 }
187 }
188
189 }
190
191 private ExtendedHttpService waitForHttpService() {
192 final ServiceTracker<ExtendedHttpService, ExtendedHttpService> st = new ServiceTracker<ExtendedHttpService, ExtendedHttpService>(
193 bundleContext, ExtendedHttpService.class, null);
194 st.open();
195 ExtendedHttpService httpService;
196 try {
197 httpService = st.waitForService(1000);
198 } catch (InterruptedException e) {
199 httpService = null;
200 }
201
202 if (httpService == null)
203 throw new CmsException("Could not find "
204 + ExtendedHttpService.class + " service.");
205 return httpService;
206 }
207
208 final private static void directorsCut(long initDuration) {
209 // final long ms = 128l + (long) (Math.random() * 128d);
210 long ms = initDuration / 100;
211 log.info("Spend " + ms + "ms"
212 + " reflecting on the progress brought to mankind"
213 + " by Free Software...");
214 long beginNano = System.nanoTime();
215 try {
216 Thread.sleep(ms, 0);
217 } catch (InterruptedException e) {
218 // silent
219 }
220 long durationNano = System.nanoTime() - beginNano;
221 final double M = 1000d * 1000d;
222 double sleepAccuracy = ((double) durationNano) / (ms * M);
223 if (log.isDebugEnabled())
224 log.debug("Sleep accuracy: "
225 + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100))
226 + " %");
227 }
228
229 /** Workaround for blocking Gogo shell by system shutdown. */
230 private class GogoShellKiller extends Thread {
231
232 public GogoShellKiller() {
233 super("Gogo shell killer");
234 setDaemon(true);
235 }
236
237 @Override
238 public void run() {
239 ThreadGroup rootTg = getRootThreadGroup(null);
240 Thread gogoShellThread = findGogoShellThread(rootTg);
241 if (gogoShellThread == null)
242 return;
243 while (getNonDaemonCount(rootTg) > 2) {
244 try {
245 Thread.sleep(100);
246 } catch (InterruptedException e) {
247 // silent
248 }
249 }
250 gogoShellThread = findGogoShellThread(rootTg);
251 if (gogoShellThread == null)
252 return;
253 System.exit(0);
254 }
255 }
256
257 private static ThreadGroup getRootThreadGroup(ThreadGroup tg) {
258 if (tg == null)
259 tg = Thread.currentThread().getThreadGroup();
260 if (tg.getParent() == null)
261 return tg;
262 else
263 return getRootThreadGroup(tg.getParent());
264 }
265
266 private static int getNonDaemonCount(ThreadGroup rootThreadGroup) {
267 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
268 rootThreadGroup.enumerate(threads);
269 int nonDameonCount = 0;
270 for (Thread t : threads)
271 if (!t.isDaemon())
272 nonDameonCount++;
273 return nonDameonCount;
274 }
275
276 private static Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
277 Thread[] threads = new Thread[rootThreadGroup.activeCount()];
278 rootThreadGroup.enumerate(threads, true);
279 for (Thread thread : threads) {
280 if (thread.getName().equals("Gogo shell"))
281 return thread;
282 }
283 return null;
284 }
285
286 }