View Javadoc
1   package org.argeo.cms.internal.kernel;
2   
3   import static org.argeo.api.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
4   import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX;
5   
6   import java.io.File;
7   import java.io.IOException;
8   import java.io.InputStreamReader;
9   import java.io.Reader;
10  import java.lang.management.ManagementFactory;
11  import java.net.URL;
12  import java.nio.file.Files;
13  import java.nio.file.Path;
14  import java.util.ArrayList;
15  import java.util.Arrays;
16  import java.util.HashSet;
17  import java.util.Hashtable;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Set;
21  
22  import javax.jcr.Repository;
23  import javax.jcr.RepositoryException;
24  import javax.jcr.Session;
25  import javax.security.auth.callback.CallbackHandler;
26  import javax.servlet.Servlet;
27  import javax.transaction.UserTransaction;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.jackrabbit.commons.cnd.CndImporter;
32  import org.apache.jackrabbit.core.RepositoryContext;
33  import org.apache.jackrabbit.core.RepositoryImpl;
34  import org.argeo.api.DataModelNamespace;
35  import org.argeo.api.NodeConstants;
36  import org.argeo.api.NodeDeployment;
37  import org.argeo.api.NodeState;
38  import org.argeo.api.security.CryptoKeyring;
39  import org.argeo.api.security.Keyring;
40  import org.argeo.cms.ArgeoNames;
41  import org.argeo.cms.CmsException;
42  import org.argeo.cms.internal.http.CmsRemotingServlet;
43  import org.argeo.cms.internal.http.CmsWebDavServlet;
44  import org.argeo.cms.internal.http.HttpUtils;
45  import org.argeo.jcr.JcrUtils;
46  import org.argeo.osgi.useradmin.UserAdminConf;
47  import org.argeo.util.LangUtils;
48  import org.eclipse.equinox.http.jetty.JettyConfigurator;
49  import org.osgi.framework.Bundle;
50  import org.osgi.framework.BundleContext;
51  import org.osgi.framework.Constants;
52  import org.osgi.framework.FrameworkUtil;
53  import org.osgi.framework.InvalidSyntaxException;
54  import org.osgi.framework.ServiceReference;
55  import org.osgi.framework.wiring.BundleCapability;
56  import org.osgi.framework.wiring.BundleWire;
57  import org.osgi.framework.wiring.BundleWiring;
58  import org.osgi.service.cm.Configuration;
59  import org.osgi.service.cm.ConfigurationAdmin;
60  import org.osgi.service.cm.ManagedService;
61  import org.osgi.service.http.HttpService;
62  import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
63  import org.osgi.service.useradmin.Group;
64  import org.osgi.service.useradmin.Role;
65  import org.osgi.service.useradmin.UserAdmin;
66  import org.osgi.util.tracker.ServiceTracker;
67  
68  /** Implementation of a CMS deployment. */
69  public class CmsDeployment implements NodeDeployment {
70  	private final Log log = LogFactory.getLog(getClass());
71  	private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
72  
73  	private DataModels dataModels;
74  	private DeployConfig deployConfig;
75  
76  	private Long availableSince;
77  
78  //	private final boolean cleanState;
79  
80  //	private NodeHttp nodeHttp;
81  	private String webDavConfig = HttpUtils.WEBDAV_CONFIG;
82  
83  	private boolean argeoDataModelExtensionsAvailable = false;
84  
85  	// Readiness
86  	private boolean nodeAvailable = false;
87  	private boolean userAdminAvailable = false;
88  	private boolean httpExpected = false;
89  	private boolean httpAvailable = false;
90  
91  	public CmsDeployment() {
92  //		ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
93  //		if (nodeStateSr == null)
94  //			throw new CmsException("No node state available");
95  
96  //		NodeState nodeState = bc.getService(nodeStateSr);
97  //		cleanState = nodeState.isClean();
98  
99  //		nodeHttp = new NodeHttp();
100 		dataModels = new DataModels(bc);
101 		initTrackers();
102 	}
103 
104 	private void initTrackers() {
105 		ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
106 
107 			@Override
108 			public HttpService addingService(ServiceReference<HttpService> sr) {
109 				httpAvailable = true;
110 				Object httpPort = sr.getProperty("http.port");
111 				Object httpsPort = sr.getProperty("https.port");
112 				log.info(httpPortsMsg(httpPort, httpsPort));
113 				checkReadiness();
114 				return super.addingService(sr);
115 			}
116 		};
117 		// httpSt.open();
118 		KernelUtils.asyncOpen(httpSt);
119 
120 		ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
121 		// repoContextSt.open();
122 		KernelUtils.asyncOpen(repoContextSt);
123 
124 		ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
125 			@Override
126 			public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
127 				UserAdmin userAdmin = super.addingService(reference);
128 				addStandardSystemRoles(userAdmin);
129 				userAdminAvailable = true;
130 				checkReadiness();
131 				return userAdmin;
132 			}
133 		};
134 		// userAdminSt.open();
135 		KernelUtils.asyncOpen(userAdminSt);
136 
137 		ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
138 				ConfigurationAdmin.class, null) {
139 			@Override
140 			public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
141 				ConfigurationAdmin configurationAdmin = bc.getService(reference);
142 				boolean isClean;
143 				try {
144 					Configuration[] confs = configurationAdmin
145 							.listConfigurations("(service.factoryPid=" + NodeConstants.NODE_USER_ADMIN_PID + ")");
146 					isClean = confs == null || confs.length == 0;
147 				} catch (Exception e) {
148 					throw new CmsException("Cannot analize clean state", e);
149 				}
150 				deployConfig = new DeployConfig(configurationAdmin, dataModels, isClean);
151 				httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
152 				try {
153 					Configuration[] configs = configurationAdmin
154 							.listConfigurations("(service.factoryPid=" + NodeConstants.NODE_USER_ADMIN_PID + ")");
155 
156 					boolean hasDomain = false;
157 					for (Configuration config : configs) {
158 						Object realm = config.getProperties().get(UserAdminConf.realm.name());
159 						if (realm != null) {
160 							log.debug("Found realm: " + realm);
161 							hasDomain = true;
162 						}
163 					}
164 					if (hasDomain) {
165 						loadIpaJaasConfiguration();
166 					}
167 				} catch (Exception e) {
168 					throw new CmsException("Cannot initialize config", e);
169 				}
170 				return super.addingService(reference);
171 			}
172 		};
173 		// confAdminSt.open();
174 		KernelUtils.asyncOpen(confAdminSt);
175 	}
176 
177 	private String httpPortsMsg(Object httpPort, Object httpsPort) {
178 		return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
179 	}
180 
181 	private void addStandardSystemRoles(UserAdmin userAdmin) {
182 		// we assume UserTransaction is already available (TODO make it more robust)
183 		UserTransaction userTransaction = bc.getService(bc.getServiceReference(UserTransaction.class));
184 		try {
185 			userTransaction.begin();
186 			Role adminRole = userAdmin.getRole(NodeConstants.ROLE_ADMIN);
187 			if (adminRole == null) {
188 				adminRole = userAdmin.createRole(NodeConstants.ROLE_ADMIN, Role.GROUP);
189 			}
190 			if (userAdmin.getRole(NodeConstants.ROLE_USER_ADMIN) == null) {
191 				Group userAdminRole = (Group) userAdmin.createRole(NodeConstants.ROLE_USER_ADMIN, Role.GROUP);
192 				userAdminRole.addMember(adminRole);
193 			}
194 			userTransaction.commit();
195 		} catch (Exception e) {
196 			try {
197 				userTransaction.rollback();
198 			} catch (Exception e1) {
199 				// silent
200 			}
201 			throw new CmsException("Cannot add standard system roles", e);
202 		}
203 	}
204 
205 	private void loadIpaJaasConfiguration() {
206 		if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
207 			String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
208 			URL url = getClass().getClassLoader().getResource(jaasConfig);
209 			KernelUtils.setJaasConfiguration(url);
210 			log.debug("Set IPA JAAS configuration.");
211 		}
212 	}
213 
214 	public void shutdown() {
215 //		if (nodeHttp != null)
216 //			nodeHttp.destroy();
217 
218 		try {
219 			for (ServiceReference<JackrabbitLocalRepository> sr : bc
220 					.getServiceReferences(JackrabbitLocalRepository.class, null)) {
221 				bc.getService(sr).destroy();
222 			}
223 		} catch (InvalidSyntaxException e1) {
224 			log.error("Cannot sclean repsoitories", e1);
225 		}
226 
227 		try {
228 			JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
229 		} catch (Exception e) {
230 			log.error("Cannot stop default Jetty server.", e);
231 		}
232 
233 		if (deployConfig != null) {
234 			new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
235 		}
236 	}
237 
238 	/**
239 	 * Checks whether the deployment is available according to expectations, and
240 	 * mark it as available.
241 	 */
242 	private synchronized void checkReadiness() {
243 		if (isAvailable())
244 			return;
245 		if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
246 			String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
247 			String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
248 			availableSince = System.currentTimeMillis();
249 			long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
250 			String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
251 			log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
252 			if (log.isDebugEnabled()) {
253 				log.debug("## state: " + state);
254 				if (data != null)
255 					log.debug("## data: " + data);
256 			}
257 			long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
258 			long initDuration = System.currentTimeMillis() - begin;
259 			if (log.isTraceEnabled())
260 				log.trace("Kernel initialization took " + initDuration + "ms");
261 			tributeToFreeSoftware(initDuration);
262 		}
263 	}
264 
265 	final private void tributeToFreeSoftware(long initDuration) {
266 		if (log.isTraceEnabled()) {
267 			long ms = initDuration / 100;
268 			log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " 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 			log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
279 		}
280 	}
281 
282 	private void prepareNodeRepository(Repository deployedNodeRepository) {
283 		if (availableSince != null) {
284 			throw new CmsException("Deployment is already available");
285 		}
286 
287 		// home
288 		prepareDataModel(NodeConstants.NODE_REPOSITORY, deployedNodeRepository);
289 	}
290 
291 	private void prepareHomeRepository(RepositoryImpl deployedRepository) {
292 		Session adminSession = KernelUtils.openAdminSession(deployedRepository);
293 		try {
294 			argeoDataModelExtensionsAvailable = Arrays
295 					.asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
296 					.contains(ArgeoNames.ARGEO_NAMESPACE);
297 		} catch (RepositoryException e) {
298 			log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
299 			argeoDataModelExtensionsAvailable = false;
300 		} finally {
301 			JcrUtils.logoutQuietly(adminSession);
302 		}
303 
304 		// Publish home with the highest service ranking
305 		Hashtable<String, Object> regProps = new Hashtable<>();
306 		regProps.put(NodeConstants.CN, NodeConstants.EGO_REPOSITORY);
307 		regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
308 		Repository egoRepository = new EgoRepository(deployedRepository, false);
309 		bc.registerService(Repository.class, egoRepository, regProps);
310 		registerRepositoryServlets(NodeConstants.EGO_REPOSITORY, egoRepository);
311 
312 		// Keyring only if Argeo extensions are available
313 		if (argeoDataModelExtensionsAvailable) {
314 			new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
315 
316 				@Override
317 				public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
318 					NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
319 					CallbackHandler callbackHandler = bc.getService(reference);
320 					nodeKeyring.setDefaultCallbackHandler(callbackHandler);
321 					bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
322 							nodeKeyring, LangUtils.dico(Constants.SERVICE_PID, NodeConstants.NODE_KEYRING_PID));
323 					return callbackHandler;
324 				}
325 
326 			}.open();
327 		}
328 	}
329 
330 	/** Session is logged out. */
331 	private void prepareDataModel(String cn, Repository repository) {
332 		Session adminSession = KernelUtils.openAdminSession(repository);
333 		try {
334 			Set<String> processed = new HashSet<String>();
335 			bundles: for (Bundle bundle : bc.getBundles()) {
336 				BundleWiring wiring = bundle.adapt(BundleWiring.class);
337 				if (wiring == null)
338 					continue bundles;
339 				if (NodeConstants.NODE_REPOSITORY.equals(cn))// process all data models
340 					processWiring(cn, adminSession, wiring, processed, false);
341 				else {
342 					List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
343 					for (BundleCapability capability : capabilities) {
344 						String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
345 						if (dataModelName.equals(cn))// process only own data model
346 							processWiring(cn, adminSession, wiring, processed, false);
347 					}
348 				}
349 			}
350 		} finally {
351 			JcrUtils.logoutQuietly(adminSession);
352 		}
353 	}
354 
355 	private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
356 			boolean importListedAbstractModels) {
357 		// recursively process requirements first
358 		List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
359 		for (BundleWire wire : requiredWires) {
360 			processWiring(cn, adminSession, wire.getProviderWiring(), processed, true);
361 		}
362 
363 		List<String> publishAsLocalRepo = new ArrayList<>();
364 		List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
365 		capabilities: for (BundleCapability capability : capabilities) {
366 			if (!importListedAbstractModels
367 					&& KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
368 				continue capabilities;
369 			}
370 			boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
371 			if (publish)
372 				publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
373 		}
374 		// Publish all at once, so that bundles with multiple CNDs are consistent
375 		for (String dataModelName : publishAsLocalRepo)
376 			publishLocalRepo(dataModelName, adminSession.getRepository());
377 	}
378 
379 	private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
380 			Set<String> processed) {
381 		Map<String, Object> attrs = capability.getAttributes();
382 		String name = (String) attrs.get(DataModelNamespace.NAME);
383 		if (processed.contains(name)) {
384 			if (log.isTraceEnabled())
385 				log.trace("Data model " + name + " has already been processed");
386 			return false;
387 		}
388 
389 		// CND
390 		String path = (String) attrs.get(DataModelNamespace.CND);
391 		if (path != null) {
392 			File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
393 			if (!dataModel.exists()) {
394 				URL url = capability.getRevision().getBundle().getResource(path);
395 				if (url == null)
396 					throw new CmsException("No data model '" + name + "' found under path " + path);
397 				try (Reader reader = new InputStreamReader(url.openStream())) {
398 					CndImporter.registerNodeTypes(reader, adminSession, true);
399 					processed.add(name);
400 					dataModel.getParentFile().mkdirs();
401 					dataModel.createNewFile();
402 					if (log.isDebugEnabled())
403 						log.debug("Registered CND " + url);
404 				} catch (Exception e) {
405 					throw new CmsException("Cannot import CND " + url, e);
406 				}
407 			}
408 		}
409 
410 		if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
411 			return false;
412 		// Non abstract
413 		boolean isStandalone = deployConfig.isStandalone(name);
414 		boolean publishLocalRepo;
415 		if (isStandalone && name.equals(cn))// includes the node itself
416 			publishLocalRepo = true;
417 		else if (!isStandalone && cn.equals(NodeConstants.NODE_REPOSITORY))
418 			publishLocalRepo = true;
419 		else
420 			publishLocalRepo = false;
421 
422 		return publishLocalRepo;
423 	}
424 
425 	private void publishLocalRepo(String dataModelName, Repository repository) {
426 		Hashtable<String, Object> properties = new Hashtable<>();
427 		properties.put(NodeConstants.CN, dataModelName);
428 		LocalRepository localRepository;
429 		String[] classes;
430 		if (repository instanceof RepositoryImpl) {
431 			localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
432 			classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
433 					JackrabbitLocalRepository.class.getName() };
434 		} else {
435 			localRepository = new LocalRepository(repository, dataModelName);
436 			classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
437 		}
438 		bc.registerService(classes, localRepository, properties);
439 
440 		// TODO make it configurable
441 		registerRepositoryServlets(dataModelName, localRepository);
442 		if (log.isTraceEnabled())
443 			log.trace("Published data model " + dataModelName);
444 	}
445 
446 	@Override
447 	public synchronized Long getAvailableSince() {
448 		return availableSince;
449 	}
450 
451 	public synchronized boolean isAvailable() {
452 		return availableSince != null;
453 	}
454 
455 	protected void registerRepositoryServlets(String alias, Repository repository) {
456 		registerRemotingServlet(alias, repository);
457 		registerWebdavServlet(alias, repository);
458 	}
459 
460 	protected void registerWebdavServlet(String alias, Repository repository) {
461 		CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
462 		Hashtable<String, String> ip = new Hashtable<>();
463 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
464 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
465 				"/" + alias);
466 
467 		ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
468 		ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
469 				"(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + NodeConstants.PATH_DATA + ")");
470 		bc.registerService(Servlet.class, webdavServlet, ip);
471 	}
472 
473 	protected void registerRemotingServlet(String alias, Repository repository) {
474 		CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
475 		Hashtable<String, String> ip = new Hashtable<>();
476 		ip.put(NodeConstants.CN, alias);
477 		// Properties ip = new Properties();
478 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
479 				"/" + alias);
480 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
481 				"Negotiate");
482 
483 		// Looks like a bug in Jackrabbit remoting init
484 		Path tmpDir;
485 		try {
486 			tmpDir = Files.createTempDirectory("remoting_" + alias);
487 		} catch (IOException e) {
488 			throw new CmsException("Cannot create temp directory for remoting servlet", e);
489 		}
490 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
491 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
492 				"remoting_" + alias);
493 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
494 				HttpUtils.DEFAULT_PROTECTED_HANDLERS);
495 		ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
496 
497 		ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
498 		ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
499 				"(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + NodeConstants.PATH_JCR + ")");
500 		bc.registerService(Servlet.class, remotingServlet, ip);
501 	}
502 
503 	private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
504 
505 		public RepositoryContextStc() {
506 			super(bc, RepositoryContext.class, null);
507 		}
508 
509 		@Override
510 		public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
511 			RepositoryContext repoContext = bc.getService(reference);
512 			String cn = (String) reference.getProperty(NodeConstants.CN);
513 			if (cn != null) {
514 				if (cn.equals(NodeConstants.NODE_REPOSITORY)) {
515 					prepareNodeRepository(repoContext.getRepository());
516 					// TODO separate home repository
517 					prepareHomeRepository(repoContext.getRepository());
518 					registerRepositoryServlets(cn, repoContext.getRepository());
519 					nodeAvailable = true;
520 					checkReadiness();
521 				} else {
522 					prepareDataModel(cn, repoContext.getRepository());
523 				}
524 			}
525 			return repoContext;
526 		}
527 
528 		@Override
529 		public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
530 		}
531 
532 		@Override
533 		public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
534 		}
535 
536 	}
537 
538 }