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