1 package org
.argeo
.cms
.jcr
.internal
;
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
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStreamReader
;
11 import java
.nio
.file
.Files
;
12 import java
.nio
.file
.Path
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Arrays
;
15 import java
.util
.Collection
;
16 import java
.util
.HashSet
;
17 import java
.util
.Hashtable
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
23 import javax
.jcr
.Repository
;
24 import javax
.jcr
.RepositoryException
;
25 import javax
.jcr
.Session
;
26 import javax
.security
.auth
.callback
.CallbackHandler
;
27 import javax
.servlet
.Servlet
;
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
.NodeUtils
;
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
.internal
.jcr
.JcrInitUtils
;
42 import org
.argeo
.cms
.jcr
.internal
.servlet
.CmsRemotingServlet
;
43 import org
.argeo
.cms
.jcr
.internal
.servlet
.CmsWebDavServlet
;
44 import org
.argeo
.cms
.jcr
.internal
.servlet
.JcrHttpUtils
;
45 import org
.argeo
.jcr
.Jcr
;
46 import org
.argeo
.jcr
.JcrException
;
47 import org
.argeo
.jcr
.JcrUtils
;
48 import org
.argeo
.naming
.LdapAttrs
;
49 import org
.argeo
.util
.LangUtils
;
50 import org
.osgi
.framework
.Bundle
;
51 import org
.osgi
.framework
.BundleContext
;
52 import org
.osgi
.framework
.Constants
;
53 import org
.osgi
.framework
.FrameworkUtil
;
54 import org
.osgi
.framework
.InvalidSyntaxException
;
55 import org
.osgi
.framework
.ServiceReference
;
56 import org
.osgi
.framework
.wiring
.BundleCapability
;
57 import org
.osgi
.framework
.wiring
.BundleWire
;
58 import org
.osgi
.framework
.wiring
.BundleWiring
;
59 import org
.osgi
.service
.cm
.ManagedService
;
60 import org
.osgi
.service
.http
.whiteboard
.HttpWhiteboardConstants
;
61 import org
.osgi
.util
.tracker
.ServiceTracker
;
63 /** Implementation of a CMS deployment. */
64 public class JcrDeployment
{
65 private final Log log
= LogFactory
.getLog(getClass());
66 private final BundleContext bc
= FrameworkUtil
.getBundle(getClass()).getBundleContext();
68 private DataModels dataModels
;
69 private String webDavConfig
= JcrHttpUtils
.WEBDAV_CONFIG
;
71 private boolean argeoDataModelExtensionsAvailable
= false;
74 private boolean nodeAvailable
= false;
76 NodeDeployment nodeDeployment
;
78 public JcrDeployment() {
79 dataModels
= new DataModels(bc
);
85 ServiceTracker
<?
, ?
> repoContextSt
= new RepositoryContextStc();
86 // repoContextSt.open();
87 KernelUtils
.asyncOpen(repoContextSt
);
89 // nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
91 JcrInitUtils
.addToDeployment(nodeDeployment
);
95 public void destroy() {
96 // if (nodeHttp != null)
97 // nodeHttp.destroy();
100 for (ServiceReference
<JackrabbitLocalRepository
> sr
: bc
101 .getServiceReferences(JackrabbitLocalRepository
.class, null)) {
102 bc
.getService(sr
).destroy();
104 } catch (InvalidSyntaxException e1
) {
105 log
.error("Cannot clean repositories", e1
);
110 public void setNodeDeployment(NodeDeployment nodeDeployment
) {
111 this.nodeDeployment
= nodeDeployment
;
115 * Checks whether the deployment is available according to expectations, and
116 * mark it as available.
118 // private synchronized void checkReadiness() {
119 // if (isAvailable())
121 // if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
122 // String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
123 // String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
124 // availableSince = System.currentTimeMillis();
125 // long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
126 // String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
127 // log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
128 // if (log.isDebugEnabled()) {
129 // log.debug("## state: " + state);
131 // log.debug("## data: " + data);
133 // long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
134 // long initDuration = System.currentTimeMillis() - begin;
135 // if (log.isTraceEnabled())
136 // log.trace("Kernel initialization took " + initDuration + "ms");
137 // tributeToFreeSoftware(initDuration);
141 private void prepareNodeRepository(Repository deployedNodeRepository
, List
<String
> publishAsLocalRepo
) {
142 // if (availableSince != null) {
143 // throw new IllegalStateException("Deployment is already available");
147 prepareDataModel(NodeConstants
.NODE_REPOSITORY
, deployedNodeRepository
, publishAsLocalRepo
);
150 // if (deployConfig.isFirstInit()) {
151 // Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
152 // if (Files.exists(restorePath)) {
153 // if (log.isDebugEnabled())
154 // log.debug("Found backup " + restorePath + ", restoring it...");
155 // LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
156 // KernelUtils.doAsDataAdmin(logicalRestore);
157 // log.info("Restored backup from " + restorePath);
161 // init from repository
162 Collection
<ServiceReference
<Repository
>> initRepositorySr
;
164 initRepositorySr
= bc
.getServiceReferences(Repository
.class,
165 "(" + NodeConstants
.CN
+ "=" + NodeConstants
.NODE_INIT
+ ")");
166 } catch (InvalidSyntaxException e1
) {
167 throw new IllegalArgumentException(e1
);
169 Iterator
<ServiceReference
<Repository
>> it
= initRepositorySr
.iterator();
170 while (it
.hasNext()) {
171 ServiceReference
<Repository
> sr
= it
.next();
172 Object labeledUri
= sr
.getProperties().get(LdapAttrs
.labeledURI
.name());
173 Repository initRepository
= bc
.getService(sr
);
174 if (log
.isDebugEnabled())
175 log
.debug("Found init repository " + labeledUri
+ ", copying it...");
176 initFromRepository(deployedNodeRepository
, initRepository
);
177 log
.info("Node repository initialised from " + labeledUri
);
181 /** Init from a (typically remote) repository. */
182 private void initFromRepository(Repository deployedNodeRepository
, Repository initRepository
) {
183 Session initSession
= null;
185 initSession
= initRepository
.login();
186 workspaces
: for (String workspaceName
: initSession
.getWorkspace().getAccessibleWorkspaceNames()) {
187 if ("security".equals(workspaceName
))
189 if (log
.isDebugEnabled())
190 log
.debug("Copying workspace " + workspaceName
+ " from init repository...");
191 long begin
= System
.currentTimeMillis();
192 Session targetSession
= null;
193 Session sourceSession
= null;
196 targetSession
= NodeUtils
.openDataAdminSession(deployedNodeRepository
, workspaceName
);
197 } catch (IllegalArgumentException e
) {// no such workspace
198 Session adminSession
= NodeUtils
.openDataAdminSession(deployedNodeRepository
, null);
200 adminSession
.getWorkspace().createWorkspace(workspaceName
);
202 Jcr
.logout(adminSession
);
204 targetSession
= NodeUtils
.openDataAdminSession(deployedNodeRepository
, workspaceName
);
206 sourceSession
= initRepository
.login(workspaceName
);
207 // JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
208 // TODO deal with referenceable nodes
209 JcrUtils
.copy(sourceSession
.getRootNode(), targetSession
.getRootNode());
210 targetSession
.save();
211 long duration
= System
.currentTimeMillis() - begin
;
212 if (log
.isDebugEnabled())
213 log
.debug("Copied workspace " + workspaceName
+ " from init repository in " + (duration
/ 1000)
215 } catch (Exception e
) {
216 log
.error("Cannot copy workspace " + workspaceName
+ " from init repository.", e
);
218 Jcr
.logout(sourceSession
);
219 Jcr
.logout(targetSession
);
222 } catch (RepositoryException e
) {
223 throw new JcrException(e
);
225 Jcr
.logout(initSession
);
229 private void prepareHomeRepository(RepositoryImpl deployedRepository
) {
230 Session adminSession
= KernelUtils
.openAdminSession(deployedRepository
);
232 argeoDataModelExtensionsAvailable
= Arrays
233 .asList(adminSession
.getWorkspace().getNamespaceRegistry().getURIs())
234 .contains(ArgeoNames
.ARGEO_NAMESPACE
);
235 } catch (RepositoryException e
) {
236 log
.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e
);
237 argeoDataModelExtensionsAvailable
= false;
239 JcrUtils
.logoutQuietly(adminSession
);
242 // Publish home with the highest service ranking
243 Hashtable
<String
, Object
> regProps
= new Hashtable
<>();
244 regProps
.put(NodeConstants
.CN
, NodeConstants
.EGO_REPOSITORY
);
245 regProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
246 Repository egoRepository
= new EgoRepository(deployedRepository
, false);
247 bc
.registerService(Repository
.class, egoRepository
, regProps
);
248 registerRepositoryServlets(NodeConstants
.EGO_REPOSITORY
, egoRepository
);
250 // Keyring only if Argeo extensions are available
251 if (argeoDataModelExtensionsAvailable
) {
252 new ServiceTracker
<CallbackHandler
, CallbackHandler
>(bc
, CallbackHandler
.class, null) {
255 public CallbackHandler
addingService(ServiceReference
<CallbackHandler
> reference
) {
256 NodeKeyRing nodeKeyring
= new NodeKeyRing(egoRepository
);
257 CallbackHandler callbackHandler
= bc
.getService(reference
);
258 nodeKeyring
.setDefaultCallbackHandler(callbackHandler
);
259 bc
.registerService(LangUtils
.names(Keyring
.class, CryptoKeyring
.class, ManagedService
.class),
260 nodeKeyring
, LangUtils
.dict(Constants
.SERVICE_PID
, NodeConstants
.NODE_KEYRING_PID
));
261 return callbackHandler
;
268 /** Session is logged out. */
269 private void prepareDataModel(String cn
, Repository repository
, List
<String
> publishAsLocalRepo
) {
270 Session adminSession
= KernelUtils
.openAdminSession(repository
);
272 Set
<String
> processed
= new HashSet
<String
>();
273 bundles
: for (Bundle bundle
: bc
.getBundles()) {
274 BundleWiring wiring
= bundle
.adapt(BundleWiring
.class);
277 if (NodeConstants
.NODE_REPOSITORY
.equals(cn
))// process all data models
278 processWiring(cn
, adminSession
, wiring
, processed
, false, publishAsLocalRepo
);
280 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
281 for (BundleCapability capability
: capabilities
) {
282 String dataModelName
= (String
) capability
.getAttributes().get(DataModelNamespace
.NAME
);
283 if (dataModelName
.equals(cn
))// process only own data model
284 processWiring(cn
, adminSession
, wiring
, processed
, false, publishAsLocalRepo
);
289 JcrUtils
.logoutQuietly(adminSession
);
293 private void processWiring(String cn
, Session adminSession
, BundleWiring wiring
, Set
<String
> processed
,
294 boolean importListedAbstractModels
, List
<String
> publishAsLocalRepo
) {
295 // recursively process requirements first
296 List
<BundleWire
> requiredWires
= wiring
.getRequiredWires(CMS_DATA_MODEL_NAMESPACE
);
297 for (BundleWire wire
: requiredWires
) {
298 processWiring(cn
, adminSession
, wire
.getProviderWiring(), processed
, true, publishAsLocalRepo
);
301 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
302 capabilities
: for (BundleCapability capability
: capabilities
) {
303 if (!importListedAbstractModels
304 && KernelUtils
.asBoolean((String
) capability
.getAttributes().get(DataModelNamespace
.ABSTRACT
))) {
305 continue capabilities
;
307 boolean publish
= registerDataModelCapability(cn
, adminSession
, capability
, processed
);
309 publishAsLocalRepo
.add((String
) capability
.getAttributes().get(DataModelNamespace
.NAME
));
313 private boolean registerDataModelCapability(String cn
, Session adminSession
, BundleCapability capability
,
314 Set
<String
> processed
) {
315 Map
<String
, Object
> attrs
= capability
.getAttributes();
316 String name
= (String
) attrs
.get(DataModelNamespace
.NAME
);
317 if (processed
.contains(name
)) {
318 if (log
.isTraceEnabled())
319 log
.trace("Data model " + name
+ " has already been processed");
324 String path
= (String
) attrs
.get(DataModelNamespace
.CND
);
326 File dataModel
= bc
.getBundle().getDataFile("dataModels/" + path
);
327 if (!dataModel
.exists()) {
328 URL url
= capability
.getRevision().getBundle().getResource(path
);
330 throw new IllegalArgumentException("No data model '" + name
+ "' found under path " + path
);
331 try (Reader reader
= new InputStreamReader(url
.openStream())) {
332 CndImporter
.registerNodeTypes(reader
, adminSession
, true);
334 dataModel
.getParentFile().mkdirs();
335 dataModel
.createNewFile();
336 if (log
.isDebugEnabled())
337 log
.debug("Registered CND " + url
);
338 } catch (Exception e
) {
339 log
.error("Cannot import CND " + url
, e
);
344 if (KernelUtils
.asBoolean((String
) attrs
.get(DataModelNamespace
.ABSTRACT
)))
347 boolean isStandalone
= isStandalone(name
);
348 boolean publishLocalRepo
;
349 if (isStandalone
&& name
.equals(cn
))// includes the node itself
350 publishLocalRepo
= true;
351 else if (!isStandalone
&& cn
.equals(NodeConstants
.NODE_REPOSITORY
))
352 publishLocalRepo
= true;
354 publishLocalRepo
= false;
356 return publishLocalRepo
;
359 boolean isStandalone(String dataModelName
) {
360 return nodeDeployment
.getProps(NodeConstants
.NODE_REPOS_FACTORY_PID
, dataModelName
) != null;
363 private void publishLocalRepo(String dataModelName
, Repository repository
) {
364 Hashtable
<String
, Object
> properties
= new Hashtable
<>();
365 properties
.put(NodeConstants
.CN
, dataModelName
);
366 LocalRepository localRepository
;
368 if (repository
instanceof RepositoryImpl
) {
369 localRepository
= new JackrabbitLocalRepository((RepositoryImpl
) repository
, dataModelName
);
370 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName(),
371 JackrabbitLocalRepository
.class.getName() };
373 localRepository
= new LocalRepository(repository
, dataModelName
);
374 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName() };
376 bc
.registerService(classes
, localRepository
, properties
);
378 // TODO make it configurable
379 registerRepositoryServlets(dataModelName
, localRepository
);
380 if (log
.isTraceEnabled())
381 log
.trace("Published data model " + dataModelName
);
385 // public synchronized Long getAvailableSince() {
386 // return availableSince;
389 // public synchronized boolean isAvailable() {
390 // return availableSince != null;
393 protected void registerRepositoryServlets(String alias
, Repository repository
) {
394 // FIXME re-enable it with a proper class loader
395 // registerRemotingServlet(alias, repository);
396 // registerWebdavServlet(alias, repository);
399 protected void registerWebdavServlet(String alias
, Repository repository
) {
400 CmsWebDavServlet webdavServlet
= new CmsWebDavServlet(alias
, repository
);
401 Hashtable
<String
, String
> ip
= new Hashtable
<>();
402 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsWebDavServlet
.INIT_PARAM_RESOURCE_CONFIG
, webDavConfig
);
403 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsWebDavServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
,
406 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_SERVLET_PATTERN
, "/" + alias
+ "/*");
407 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_SELECT
,
408 "(" + HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_PATH
+ "=" + NodeConstants
.PATH_DATA
+ ")");
409 bc
.registerService(Servlet
.class, webdavServlet
, ip
);
412 protected void registerRemotingServlet(String alias
, Repository repository
) {
413 CmsRemotingServlet remotingServlet
= new CmsRemotingServlet(alias
, repository
);
414 Hashtable
<String
, String
> ip
= new Hashtable
<>();
415 ip
.put(NodeConstants
.CN
, alias
);
416 // Properties ip = new Properties();
417 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
,
419 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_AUTHENTICATE_HEADER
,
422 // Looks like a bug in Jackrabbit remoting init
425 tmpDir
= Files
.createTempDirectory("remoting_" + alias
);
426 } catch (IOException e
) {
427 throw new RuntimeException("Cannot create temp directory for remoting servlet", e
);
429 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_HOME
, tmpDir
.toString());
430 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_TMP_DIRECTORY
,
431 "remoting_" + alias
);
432 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_PROTECTED_HANDLERS_CONFIG
,
433 JcrHttpUtils
.DEFAULT_PROTECTED_HANDLERS
);
434 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_CREATE_ABSOLUTE_URI
, "false");
436 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_SERVLET_PATTERN
, "/" + alias
+ "/*");
437 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_SELECT
,
438 "(" + HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_PATH
+ "=" + NodeConstants
.PATH_JCR
+ ")");
439 bc
.registerService(Servlet
.class, remotingServlet
, ip
);
442 private class RepositoryContextStc
extends ServiceTracker
<RepositoryContext
, RepositoryContext
> {
444 public RepositoryContextStc() {
445 super(bc
, RepositoryContext
.class, null);
449 public RepositoryContext
addingService(ServiceReference
<RepositoryContext
> reference
) {
450 RepositoryContext repoContext
= bc
.getService(reference
);
451 String cn
= (String
) reference
.getProperty(NodeConstants
.CN
);
453 List
<String
> publishAsLocalRepo
= new ArrayList
<>();
454 if (cn
.equals(NodeConstants
.NODE_REPOSITORY
)) {
455 // JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
456 prepareNodeRepository(repoContext
.getRepository(), publishAsLocalRepo
);
457 // TODO separate home repository
458 prepareHomeRepository(repoContext
.getRepository());
459 registerRepositoryServlets(cn
, repoContext
.getRepository());
460 nodeAvailable
= true;
463 prepareDataModel(cn
, repoContext
.getRepository(), publishAsLocalRepo
);
465 // Publish all at once, so that bundles with multiple CNDs are consistent
466 for (String dataModelName
: publishAsLocalRepo
)
467 publishLocalRepo(dataModelName
, repoContext
.getRepository());
473 public void modifiedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {
477 public void removedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {