1 package org
.argeo
.cms
.jcr
.internal
;
3 import static org
.argeo
.cms
.osgi
.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
.HashSet
;
16 import java
.util
.Hashtable
;
17 import java
.util
.List
;
21 import javax
.jcr
.Repository
;
22 import javax
.jcr
.RepositoryException
;
23 import javax
.jcr
.Session
;
24 import javax
.servlet
.Servlet
;
26 import org
.apache
.jackrabbit
.commons
.cnd
.CndImporter
;
27 import org
.apache
.jackrabbit
.core
.RepositoryContext
;
28 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
29 import org
.argeo
.api
.acr
.spi
.ProvidedRepository
;
30 import org
.argeo
.api
.cms
.CmsConstants
;
31 import org
.argeo
.api
.cms
.CmsLog
;
32 import org
.argeo
.cms
.ArgeoNames
;
33 import org
.argeo
.cms
.jcr
.CmsJcrUtils
;
34 import org
.argeo
.cms
.jcr
.acr
.JcrContentNamespace
;
35 import org
.argeo
.cms
.jcr
.internal
.servlet
.CmsRemotingServlet
;
36 import org
.argeo
.cms
.jcr
.internal
.servlet
.CmsWebDavServlet
;
37 import org
.argeo
.cms
.jcr
.internal
.servlet
.JcrHttpUtils
;
38 import org
.argeo
.cms
.osgi
.DataModelNamespace
;
39 import org
.argeo
.jcr
.Jcr
;
40 import org
.argeo
.jcr
.JcrException
;
41 import org
.argeo
.jcr
.JcrUtils
;
42 import org
.osgi
.framework
.Bundle
;
43 import org
.osgi
.framework
.BundleContext
;
44 import org
.osgi
.framework
.Constants
;
45 import org
.osgi
.framework
.InvalidSyntaxException
;
46 import org
.osgi
.framework
.ServiceReference
;
47 import org
.osgi
.framework
.wiring
.BundleCapability
;
48 import org
.osgi
.framework
.wiring
.BundleWire
;
49 import org
.osgi
.framework
.wiring
.BundleWiring
;
50 import org
.osgi
.service
.http
.whiteboard
.HttpWhiteboardConstants
;
51 import org
.osgi
.util
.tracker
.ServiceTracker
;
53 /** Implementation of a CMS deployment. */
54 public class CmsJcrDeployment
{
55 private final CmsLog log
= CmsLog
.getLog(getClass());
56 private BundleContext bc
;
58 private DataModels dataModels
;
59 private String webDavConfig
= JcrHttpUtils
.WEBDAV_CONFIG
;
61 private boolean argeoDataModelExtensionsAvailable
= false;
64 private boolean nodeAvailable
= false;
66 private ProvidedRepository contentRepository
;
68 public CmsJcrDeployment() {
71 // CmsDeployment cmsDeployment;
72 public void start(BundleContext bundleContext
) {
73 // Bundle bundle = FrameworkUtil.getBundle(CmsJcrDeployment.class);
75 dataModels
= new DataModels(bc
);
77 contentRepository
.registerTypes(JcrContentNamespace
.values());
79 ServiceTracker
<?
, ?
> repoContextSt
= new RepositoryContextStc();
81 // KernelUtils.asyncOpen(repoContextSt);
83 // nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
85 // JcrInitUtils.addToDeployment(cmsDeployment);
87 // contentRepository.registerTypes(NamespaceRegistry.PREFIX_JCR, NamespaceRegistry.NAMESPACE_JCR, null);
88 // contentRepository.registerTypes(NamespaceRegistry.PREFIX_MIX, NamespaceRegistry.NAMESPACE_MIX, null);
89 // contentRepository.registerTypes(NamespaceRegistry.PREFIX_NT, NamespaceRegistry.NAMESPACE_NT, null);
92 // // https://jackrabbit.apache.org/archive/wiki/JCR/NamespaceRegistry_115513459.html
93 // contentRepository.registerTypes("rep", "internal", null);
98 // if (nodeHttp != null)
99 // nodeHttp.destroy();
102 for (ServiceReference
<JackrabbitLocalRepository
> sr
: bc
103 .getServiceReferences(JackrabbitLocalRepository
.class, null)) {
104 bc
.getService(sr
).destroy();
106 } catch (InvalidSyntaxException e1
) {
107 log
.error("Cannot clean repositories", e1
);
112 // public void setCmsDeployment(CmsDeployment cmsDeployment) {
113 // this.cmsDeployment = cmsDeployment;
117 * Checks whether the deployment is available according to expectations, and
118 * mark it as available.
120 // private synchronized void checkReadiness() {
121 // if (isAvailable())
123 // if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
124 // String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
125 // String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
126 // availableSince = System.currentTimeMillis();
127 // long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
128 // String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
129 // log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
130 // if (log.isDebugEnabled()) {
131 // log.debug("## state: " + state);
133 // log.debug("## data: " + data);
135 // long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
136 // long initDuration = System.currentTimeMillis() - begin;
137 // if (log.isTraceEnabled())
138 // log.trace("Kernel initialization took " + initDuration + "ms");
139 // tributeToFreeSoftware(initDuration);
143 private void prepareNodeRepository(Repository deployedNodeRepository
, List
<String
> publishAsLocalRepo
) {
144 // if (availableSince != null) {
145 // throw new IllegalStateException("Deployment is already available");
149 prepareDataModel(CmsConstants
.NODE_REPOSITORY
, deployedNodeRepository
, publishAsLocalRepo
);
152 // if (deployConfig.isFirstInit()) {
153 // Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
154 // if (Files.exists(restorePath)) {
155 // if (log.isDebugEnabled())
156 // log.debug("Found backup " + restorePath + ", restoring it...");
157 // LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
158 // KernelUtils.doAsDataAdmin(logicalRestore);
159 // log.info("Restored backup from " + restorePath);
163 // init from repository
164 // Collection<ServiceReference<Repository>> initRepositorySr;
166 // initRepositorySr = bc.getServiceReferences(Repository.class,
167 // "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
168 // } catch (InvalidSyntaxException e1) {
169 // throw new IllegalArgumentException(e1);
171 // Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
172 // while (it.hasNext()) {
173 // ServiceReference<Repository> sr = it.next();
174 // Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
175 // Repository initRepository = bc.getService(sr);
176 // if (log.isDebugEnabled())
177 // log.debug("Found init repository " + labeledUri + ", copying it...");
178 // initFromRepository(deployedNodeRepository, initRepository);
179 // log.info("Node repository initialised from " + labeledUri);
183 /** Init from a (typically remote) repository. */
184 private void initFromRepository(Repository deployedNodeRepository
, Repository initRepository
) {
185 Session initSession
= null;
187 initSession
= initRepository
.login();
188 workspaces
: for (String workspaceName
: initSession
.getWorkspace().getAccessibleWorkspaceNames()) {
189 if ("security".equals(workspaceName
))
191 if (log
.isDebugEnabled())
192 log
.debug("Copying workspace " + workspaceName
+ " from init repository...");
193 long begin
= System
.currentTimeMillis();
194 Session targetSession
= null;
195 Session sourceSession
= null;
198 targetSession
= CmsJcrUtils
.openDataAdminSession(deployedNodeRepository
, workspaceName
);
199 } catch (IllegalArgumentException e
) {// no such workspace
200 Session adminSession
= CmsJcrUtils
.openDataAdminSession(deployedNodeRepository
, null);
202 adminSession
.getWorkspace().createWorkspace(workspaceName
);
204 Jcr
.logout(adminSession
);
206 targetSession
= CmsJcrUtils
.openDataAdminSession(deployedNodeRepository
, workspaceName
);
208 sourceSession
= initRepository
.login(workspaceName
);
209 // JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
210 // TODO deal with referenceable nodes
211 JcrUtils
.copy(sourceSession
.getRootNode(), targetSession
.getRootNode());
212 targetSession
.save();
213 long duration
= System
.currentTimeMillis() - begin
;
214 if (log
.isDebugEnabled())
215 log
.debug("Copied workspace " + workspaceName
+ " from init repository in " + (duration
/ 1000)
217 } catch (Exception e
) {
218 log
.error("Cannot copy workspace " + workspaceName
+ " from init repository.", e
);
220 Jcr
.logout(sourceSession
);
221 Jcr
.logout(targetSession
);
224 } catch (RepositoryException e
) {
225 throw new JcrException(e
);
227 Jcr
.logout(initSession
);
231 private void prepareHomeRepository(RepositoryImpl deployedRepository
) {
232 Session adminSession
= KernelUtils
.openAdminSession(deployedRepository
);
234 argeoDataModelExtensionsAvailable
= Arrays
235 .asList(adminSession
.getWorkspace().getNamespaceRegistry().getURIs())
236 .contains(ArgeoNames
.ARGEO_NAMESPACE
);
237 } catch (RepositoryException e
) {
238 log
.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e
);
239 argeoDataModelExtensionsAvailable
= false;
241 JcrUtils
.logoutQuietly(adminSession
);
244 // Publish home with the highest service ranking
245 Hashtable
<String
, Object
> regProps
= new Hashtable
<>();
246 regProps
.put(CmsConstants
.CN
, CmsConstants
.EGO_REPOSITORY
);
247 regProps
.put(Constants
.SERVICE_RANKING
, Integer
.MAX_VALUE
);
248 Repository egoRepository
= new EgoRepository(deployedRepository
, false);
249 bc
.registerService(Repository
.class, egoRepository
, regProps
);
250 registerRepositoryServlets(CmsConstants
.EGO_REPOSITORY
, egoRepository
);
252 // Keyring only if Argeo extensions are available
253 // if (argeoDataModelExtensionsAvailable) {
254 // new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
257 // public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
258 // NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
259 // CallbackHandler callbackHandler = bc.getService(reference);
260 // nodeKeyring.setDefaultCallbackHandler(callbackHandler);
261 // bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
262 // nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
263 // return callbackHandler;
270 /** Session is logged out. */
271 private void prepareDataModel(String cn
, Repository repository
, List
<String
> publishAsLocalRepo
) {
272 Session adminSession
= KernelUtils
.openAdminSession(repository
);
274 Set
<String
> processed
= new HashSet
<String
>();
275 bundles
: for (Bundle bundle
: bc
.getBundles()) {
276 BundleWiring wiring
= bundle
.adapt(BundleWiring
.class);
279 if (CmsConstants
.NODE_REPOSITORY
.equals(cn
))// process all data models
280 processWiring(cn
, adminSession
, wiring
, processed
, false, publishAsLocalRepo
);
282 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
283 for (BundleCapability capability
: capabilities
) {
284 String dataModelName
= (String
) capability
.getAttributes().get(DataModelNamespace
.NAME
);
285 if (dataModelName
.equals(cn
))// process only own data model
286 processWiring(cn
, adminSession
, wiring
, processed
, false, publishAsLocalRepo
);
291 JcrUtils
.logoutQuietly(adminSession
);
295 private void processWiring(String cn
, Session adminSession
, BundleWiring wiring
, Set
<String
> processed
,
296 boolean importListedAbstractModels
, List
<String
> publishAsLocalRepo
) {
297 // recursively process requirements first
298 List
<BundleWire
> requiredWires
= wiring
.getRequiredWires(CMS_DATA_MODEL_NAMESPACE
);
299 for (BundleWire wire
: requiredWires
) {
300 processWiring(cn
, adminSession
, wire
.getProviderWiring(), processed
, true, publishAsLocalRepo
);
303 List
<BundleCapability
> capabilities
= wiring
.getCapabilities(CMS_DATA_MODEL_NAMESPACE
);
304 capabilities
: for (BundleCapability capability
: capabilities
) {
305 if (!importListedAbstractModels
306 && KernelUtils
.asBoolean((String
) capability
.getAttributes().get(DataModelNamespace
.ABSTRACT
))) {
307 continue capabilities
;
309 boolean publish
= registerDataModelCapability(cn
, adminSession
, capability
, processed
);
311 publishAsLocalRepo
.add((String
) capability
.getAttributes().get(DataModelNamespace
.NAME
));
315 private boolean registerDataModelCapability(String cn
, Session adminSession
, BundleCapability capability
,
316 Set
<String
> processed
) {
317 Map
<String
, Object
> attrs
= capability
.getAttributes();
318 String name
= (String
) attrs
.get(DataModelNamespace
.NAME
);
319 if (processed
.contains(name
)) {
320 if (log
.isTraceEnabled())
321 log
.trace("Data model " + name
+ " has already been processed");
326 String path
= (String
) attrs
.get(DataModelNamespace
.CND
);
328 File dataModel
= bc
.getBundle().getDataFile("dataModels/" + path
);
329 if (!dataModel
.exists()) {
330 URL url
= capability
.getRevision().getBundle().getResource(path
);
332 throw new IllegalArgumentException("No data model '" + name
+ "' found under path " + path
);
333 try (Reader reader
= new InputStreamReader(url
.openStream())) {
334 CndImporter
.registerNodeTypes(reader
, adminSession
, true);
336 dataModel
.getParentFile().mkdirs();
337 dataModel
.createNewFile();
338 if (log
.isDebugEnabled())
339 log
.debug("Registered CND " + url
);
340 } catch (Exception e
) {
341 log
.error("Cannot import CND " + url
, e
);
346 if (KernelUtils
.asBoolean((String
) attrs
.get(DataModelNamespace
.ABSTRACT
)))
349 boolean isStandalone
= isStandalone(name
);
350 boolean publishLocalRepo
;
351 if (isStandalone
&& name
.equals(cn
))// includes the node itself
352 publishLocalRepo
= true;
353 else if (!isStandalone
&& cn
.equals(CmsConstants
.NODE_REPOSITORY
))
354 publishLocalRepo
= true;
356 publishLocalRepo
= false;
358 return publishLocalRepo
;
361 boolean isStandalone(String dataModelName
) {
363 // return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID,
364 // dataModelName) != null;
367 private void publishLocalRepo(String dataModelName
, Repository repository
) {
368 Hashtable
<String
, Object
> properties
= new Hashtable
<>();
369 properties
.put(CmsConstants
.CN
, dataModelName
);
370 LocalRepository localRepository
;
372 if (repository
instanceof RepositoryImpl
) {
373 localRepository
= new JackrabbitLocalRepository((RepositoryImpl
) repository
, dataModelName
);
374 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName(),
375 JackrabbitLocalRepository
.class.getName() };
377 localRepository
= new LocalRepository(repository
, dataModelName
);
378 classes
= new String
[] { Repository
.class.getName(), LocalRepository
.class.getName() };
380 bc
.registerService(classes
, localRepository
, properties
);
382 // TODO make it configurable
383 registerRepositoryServlets(dataModelName
, localRepository
);
384 if (log
.isTraceEnabled())
385 log
.trace("Published data model " + dataModelName
);
389 // public synchronized Long getAvailableSince() {
390 // return availableSince;
393 // public synchronized boolean isAvailable() {
394 // return availableSince != null;
397 protected void registerRepositoryServlets(String alias
, Repository repository
) {
398 registerRemotingServlet(alias
, repository
);
399 registerWebdavServlet(alias
, repository
);
402 protected void registerWebdavServlet(String alias
, Repository repository
) {
403 CmsWebDavServlet webdavServlet
= new CmsWebDavServlet(alias
, repository
);
404 Hashtable
<String
, String
> ip
= new Hashtable
<>();
405 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsWebDavServlet
.INIT_PARAM_RESOURCE_CONFIG
, webDavConfig
);
406 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsWebDavServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
,
409 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_SERVLET_PATTERN
, "/" + alias
+ "/*");
410 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_SELECT
,
411 "(" + HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_PATH
+ "=" + CmsConstants
.PATH_DATA
+ ")");
412 bc
.registerService(Servlet
.class, webdavServlet
, ip
);
415 protected void registerRemotingServlet(String alias
, Repository repository
) {
416 CmsRemotingServlet remotingServlet
= new CmsRemotingServlet(alias
, repository
);
417 Hashtable
<String
, String
> ip
= new Hashtable
<>();
418 ip
.put(CmsConstants
.CN
, alias
);
419 // Properties ip = new Properties();
420 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_RESOURCE_PATH_PREFIX
,
422 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_AUTHENTICATE_HEADER
,
425 // Looks like a bug in Jackrabbit remoting init
428 tmpDir
= Files
.createTempDirectory("remoting_" + alias
);
429 } catch (IOException e
) {
430 throw new RuntimeException("Cannot create temp directory for remoting servlet", e
);
432 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_HOME
, tmpDir
.toString());
433 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_TMP_DIRECTORY
,
434 "remoting_" + alias
);
435 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_PROTECTED_HANDLERS_CONFIG
,
436 JcrHttpUtils
.DEFAULT_PROTECTED_HANDLERS
);
437 ip
.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX
+ CmsRemotingServlet
.INIT_PARAM_CREATE_ABSOLUTE_URI
, "false");
439 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_SERVLET_PATTERN
, "/" + alias
+ "/*");
440 ip
.put(HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_SELECT
,
441 "(" + HttpWhiteboardConstants
.HTTP_WHITEBOARD_CONTEXT_PATH
+ "=" + CmsConstants
.PATH_JCR
+ ")");
442 bc
.registerService(Servlet
.class, remotingServlet
, ip
);
445 public void setContentRepository(ProvidedRepository contentRepository
) {
446 this.contentRepository
= contentRepository
;
449 private class RepositoryContextStc
extends ServiceTracker
<RepositoryContext
, RepositoryContext
> {
451 public RepositoryContextStc() {
452 super(bc
, RepositoryContext
.class, null);
456 public RepositoryContext
addingService(ServiceReference
<RepositoryContext
> reference
) {
457 RepositoryContext repoContext
= bc
.getService(reference
);
458 String cn
= (String
) reference
.getProperty(CmsConstants
.CN
);
460 List
<String
> publishAsLocalRepo
= new ArrayList
<>();
461 if (cn
.equals(CmsConstants
.NODE_REPOSITORY
)) {
462 // JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
463 prepareNodeRepository(repoContext
.getRepository(), publishAsLocalRepo
);
464 // TODO separate home repository
465 prepareHomeRepository(repoContext
.getRepository());
466 registerRepositoryServlets(cn
, repoContext
.getRepository());
467 nodeAvailable
= true;
470 prepareDataModel(cn
, repoContext
.getRepository(), publishAsLocalRepo
);
472 // Publish all at once, so that bundles with multiple CNDs are consistent
473 for (String dataModelName
: publishAsLocalRepo
)
474 publishLocalRepo(dataModelName
, repoContext
.getRepository());
480 public void modifiedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {
484 public void removedService(ServiceReference
<RepositoryContext
> reference
, RepositoryContext service
) {