View Javadoc
1   /**
2    * This file Copyright (c) 2003-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.jackrabbit;
35  
36  import info.magnolia.cms.beans.config.ContentRepository;
37  import info.magnolia.cms.core.FileSystemHelper;
38  import info.magnolia.cms.core.SystemProperty;
39  import info.magnolia.objectfactory.Components;
40  import info.magnolia.repository.Provider;
41  import info.magnolia.repository.RepositoryNotInitializedException;
42  import info.magnolia.repository.definition.RepositoryDefinition;
43  
44  import java.io.File;
45  import java.io.FileInputStream;
46  import java.io.FileNotFoundException;
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.lang.reflect.InvocationTargetException;
50  import java.lang.reflect.Method;
51  import java.util.Hashtable;
52  import java.util.Iterator;
53  import java.util.Map;
54  
55  import javax.inject.Inject;
56  import javax.jcr.NamespaceException;
57  import javax.jcr.Repository;
58  import javax.jcr.RepositoryException;
59  import javax.jcr.Session;
60  import javax.jcr.SimpleCredentials;
61  import javax.jcr.Workspace;
62  import javax.jcr.nodetype.NoSuchNodeTypeException;
63  import javax.jcr.nodetype.NodeTypeManager;
64  import javax.naming.Context;
65  import javax.naming.InitialContext;
66  import javax.naming.NameNotFoundException;
67  import javax.naming.NamingException;
68  import javax.xml.transform.TransformerFactoryConfigurationError;
69  
70  import org.apache.commons.beanutils.PropertyUtils;
71  import org.apache.commons.io.IOUtils;
72  import org.apache.commons.lang3.ArrayUtils;
73  import org.apache.commons.lang3.JavaVersion;
74  import org.apache.commons.lang3.StringUtils;
75  import org.apache.commons.lang3.SystemUtils;
76  import org.apache.jackrabbit.core.WorkspaceImpl;
77  import org.apache.jackrabbit.core.jndi.RegistryHelper;
78  import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
79  import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
80  import org.apache.jackrabbit.core.nodetype.xml.NodeTypeReader;
81  import org.apache.jackrabbit.spi.Name;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  
85  
86  /**
87   * Provider implementation for Apache JackRabbit JCR repository.
88   */
89  public class ProviderImpl implements Provider {
90  
91      /**
92       * Magnolia property (entry in magnolia.properties) with the cluster id, which will be passed to jackrabbit.
93       */
94      private static final String MAGNOLIA_CLUSTERID_PROPERTY = "magnolia.clusterid";
95  
96      /**
97       * Jackrabbit system property for cluster node id.
98       */
99      private static final String JACKRABBIT_CLUSTER_ID_PROPERTY = "org.apache.jackrabbit.core.cluster.node_id";
100 
101     private static final Logger log = LoggerFactory.getLogger(ProviderImpl.class);
102 
103     private static final String CONFIG_FILENAME_KEY = "configFile";
104 
105     private static final String REPOSITORY_HOME_KEY = "repositoryHome";
106 
107     private static final String CONTEXT_FACTORY_CLASS_KEY = "contextFactoryClass";
108 
109     private static final String PROVIDER_URL_KEY = "providerURL";
110 
111     private static final String BIND_NAME_KEY = "bindName";
112 
113     private static final String MGNL_NODETYPES = "/mgnl-nodetypes/magnolia-nodetypes.xml";
114 
115     private static final String CUSTOM_NODETYPES = "customNodeTypes";
116 
117     private RepositoryDefinition repositoryMapping;
118 
119     private Repository repository;
120 
121     private String bindName;
122 
123     private Hashtable<String, Object> jndiEnv;
124 
125     private static final String REPO_HOME_PREFIX = "${repository.home}";
126 
127     private static final int REPO_HOME_SUFIX_LEN = REPO_HOME_PREFIX.length();
128 
129     private static final String sysRepositoryHome = System.getProperty("repository.home");
130 
131     private static final String sysRepositoryHomes = System.getProperty("repository.homes");
132 
133     private final FileSystemHelper fileSystemHelper;
134 
135     @Inject
136     public ProviderImpl(FileSystemHelper fileSystemHelper) {
137         this.fileSystemHelper = fileSystemHelper;
138     }
139 
140     @Deprecated
141     public ProviderImpl() {
142         this(Components.getComponent(FileSystemHelper.class));
143     }
144 
145     /**
146      * Finds the physical path to the repository folder.
147      *
148      * @param repositoryHome the property set in the repository.xml file
149      * @return the full path resolved to the repository dir
150      */
151     private String getRepositoryHome(final String repositoryHome) {
152         boolean relocate = false;
153         String tmp = repositoryHome;
154         if (repositoryHome.startsWith(REPOSITORY_HOME_KEY)) {
155             tmp = repositoryHome.substring(REPO_HOME_SUFIX_LEN);
156             relocate = true;
157         }
158         /*
159          * Resolve if the path started with the suffix
160          */
161         if (sysRepositoryHome != null && relocate) {
162             return sysRepositoryHome + File.separator + tmp;
163         }
164 
165         /*
166          * This is apply to all repositories if the java property is set
167          */
168         if (sysRepositoryHomes != null) {
169             return sysRepositoryHomes + File.separator + tmp;
170         }
171 
172         /*
173          * Return the same value as before if neither of the above applied
174          */
175         return fileSystemHelper.getAbsoluteFileSystemPath(tmp);
176     }
177 
178     /**
179      * @see info.magnolia.repository.Provider#init(info.magnolia.repository.definition.RepositoryDefinition)
180      */
181     @Override
182     public void init(RepositoryDefinition repositoryMapping) throws RepositoryNotInitializedException {
183         checkXmlSettings();
184 
185         this.repositoryMapping = repositoryMapping;
186         /* connect to repository */
187         Map params = this.repositoryMapping.getParameters();
188         String configFile = (String) params.get(CONFIG_FILENAME_KEY);
189         configFile = fileSystemHelper.getAbsoluteFileSystemPath(configFile);
190         String repositoryHome = (String) params.get(REPOSITORY_HOME_KEY);
191         repositoryHome = getRepositoryHome(repositoryHome);
192 
193         // cleanup the path, to remove eventual ../.. and make it absolute
194         try {
195             File repoHomeFile = new File(repositoryHome);
196             repositoryHome = repoHomeFile.getCanonicalPath();
197         } catch (IOException e1) {
198             // should never happen and it's not a problem at this point, just pass it to jackrabbit and see
199         }
200 
201         String clusterid = SystemProperty.getProperty(MAGNOLIA_CLUSTERID_PROPERTY);
202         if (StringUtils.isNotBlank(clusterid)) {
203             System.setProperty(JACKRABBIT_CLUSTER_ID_PROPERTY, clusterid);
204         }
205 
206         // get it back from system properties, if it has been set elsewhere
207         clusterid = System.getProperty(JACKRABBIT_CLUSTER_ID_PROPERTY);
208 
209         log.info("Loading repository at {} (config file: {}) - cluster id: \"{}\"", repositoryHome, configFile, StringUtils.defaultString(clusterid, "<unset>"));
210 
211         bindName = (String) params.get(BIND_NAME_KEY);
212         jndiEnv = new Hashtable<String, Object>();
213         jndiEnv.put(Context.INITIAL_CONTEXT_FACTORY, params.get(CONTEXT_FACTORY_CLASS_KEY));
214         jndiEnv.put(Context.PROVIDER_URL, params.get(PROVIDER_URL_KEY));
215 
216         try {
217             InitialContext ctx = new InitialContext(jndiEnv);
218             // first try to find the existing object if any
219             try {
220                 this.repository = (Repository) ctx.lookup(bindName);
221             } catch (NameNotFoundException ne) {
222                 log.debug("No JNDI bound Repository found with name {}, trying to initialize a new Repository", bindName);
223                 RegistryHelper.registerRepository(ctx, bindName, configFile, repositoryHome, true);
224                 this.repository = (Repository) ctx.lookup(bindName);
225             }
226             this.validateWorkspaces();
227         } catch (NamingException e) {
228             log.error("Unable to initialize repository: {}", e.getMessage(), e);
229             throw new RepositoryNotInitializedException(e);
230         } catch (RepositoryException e) {
231             log.error("Unable to initialize repository: {}", e.getMessage(), e);
232             throw new RepositoryNotInitializedException(e);
233         } catch (TransformerFactoryConfigurationError e) {
234             log.error("Unable to initialize repository: {}", e.getMessage(), e);
235             throw new RepositoryNotInitializedException(e);
236         }
237     }
238 
239     @Override
240     public void shutdownRepository() {
241         log.info("Shutting down repository bound to '{}'", bindName);
242 
243         try {
244             Context ctx = new InitialContext(jndiEnv);
245             RegistryHelper.unregisterRepository(ctx, bindName);
246         } catch (NamingException e) {
247             log.warn("Unable to shutdown repository {}: {}", bindName, e.getMessage(), e);
248         } catch (Throwable e) {
249             log.error("Failed to shutdown repository {}: {}", bindName, e.getMessage(), e);
250         }
251     }
252 
253     @Override
254     public Repository getUnderlyingRepository() throws RepositoryNotInitializedException {
255         if (this.repository == null) {
256             throw new RepositoryNotInitializedException("Null repository");
257         }
258         return this.repository;
259     }
260 
261     /**
262      * @see info.magnolia.repository.Provider#registerNamespace(java.lang.String, java.lang.String, javax.jcr.Workspace)
263      */
264     @Override
265     public void registerNamespace(String namespacePrefix, String uri, Workspace workspace) throws RepositoryException {
266         try {
267             workspace.getNamespaceRegistry().getURI(namespacePrefix);
268         } catch (NamespaceException e) {
269             log.debug(e.getMessage());
270 
271             log.info("Registering prefix [{}] with URI {}", namespacePrefix, uri);
272             workspace.getNamespaceRegistry().registerNamespace(namespacePrefix, uri);
273         }
274     }
275 
276     /**
277      * @see info.magnolia.repository.Provider#unregisterNamespace(java.lang.String, javax.jcr.Workspace)
278      */
279     @Override
280     public void unregisterNamespace(String prefix, Workspace workspace) throws RepositoryException {
281         workspace.getNamespaceRegistry().unregisterNamespace(prefix);
282     }
283 
284     /**
285      * @see info.magnolia.repository.Provider#registerNodeTypes(String)
286      */
287     @Override
288     public void registerNodeTypes() throws RepositoryException {
289         registerNodeTypes(StringUtils.EMPTY);
290     }
291 
292     /**
293      * @see info.magnolia.repository.Provider#registerNodeTypes(java.lang.String)
294      */
295     @Override
296     public void registerNodeTypes(String configuration) throws RepositoryException {
297         if (StringUtils.isEmpty(configuration)) {
298             configuration = this.repositoryMapping.getParameters().get(CUSTOM_NODETYPES);
299         }
300 
301         InputStream xml = getNodeTypeDefinition(configuration);
302         this.registerNodeTypes(xml);
303     }
304 
305     /**
306      * @see info.magnolia.repository.Provider#registerNodeTypes(java.io.InputStream)
307      */
308     @Override
309     public void registerNodeTypes(InputStream xmlStream) throws RepositoryException {
310         SimpleCredentials credentials = new SimpleCredentials(
311                 ContentRepository.REPOSITORY_USER,
312                 ContentRepository.REPOSITORY_PSWD.toCharArray());
313         Session jcrSession = this.repository.login(credentials);
314 
315         try {
316 
317             Workspace workspace = jcrSession.getWorkspace();
318 
319             // should never happen
320             if (xmlStream == null) {
321                 throw new MissingNodetypesException();
322             }
323 
324             // Use Objects so that it works both with jackrabbit 1.x (NodeTypeDef) and jackrabbit 2
325             // (QNodeTypeDefinition)
326             Object[] types;
327 
328             try {
329                 types = (Object[]) NodeTypeReader.class.getMethod("read", new Class[]{InputStream.class}).invoke(null, xmlStream);
330             } catch (Exception e) {
331                 throw new RepositoryException(e.getMessage(), e);
332             } finally {
333                 IOUtils.closeQuietly(xmlStream);
334             }
335 
336             NodeTypeManager ntMgr = workspace.getNodeTypeManager();
337             NodeTypeRegistry ntReg;
338             try {
339                 ntReg = ((NodeTypeManagerImpl) ntMgr).getNodeTypeRegistry();
340             } catch (ClassCastException e) {
341                 // this could happen if the repository provider does not have proper Shared API for the
342                 // application server like at the moment in Jackrabbit
343                 log.debug("Failed to get NodeTypeRegistry: ", e);
344                 return;
345             }
346 
347             for (int j = 0; j < types.length; j++) {
348                 Object def = types[j];
349 
350                 Name ntname;
351                 try {
352                     ntname = (Name) PropertyUtils.getProperty(def, "name");
353                 } catch (Exception e) {
354                     throw new RepositoryException(e.getMessage(), e);
355                 }
356 
357                 try {
358 
359                     // return value has changed in jackrabbit 2, we still have to use reflection here
360                     // ntReg.getNodeTypeDef(ntname);
361 
362                     Method method = ntReg.getClass().getMethod("getNodeTypeDef", Name.class);
363                     method.invoke(ntReg, ntname);
364                 } catch (IllegalArgumentException e) {
365                     throw new RepositoryException(e.getMessage(), e);
366                 } catch (IllegalAccessException e) {
367                     throw new RepositoryException(e.getMessage(), e);
368                 } catch (SecurityException e) {
369                     throw new RepositoryException(e.getMessage(), e);
370                 } catch (NoSuchMethodException e) {
371                     throw new RepositoryException(e.getMessage(), e);
372                 } catch (InvocationTargetException ite) {
373                     if (ite.getTargetException() instanceof NoSuchNodeTypeException) {
374                         log.info("Registering nodetype {} on repository {}", ntname, repositoryMapping.getName());
375 
376                         try {
377                             // reflection for jackrabbit 1+2 compatibility
378                             getMethod(NodeTypeRegistry.class, "registerNodeType").invoke(ntReg, def);
379                         } catch (Exception e) {
380                             throw new RepositoryException(e.getMessage(), e);
381                         }
382                     }
383                 }
384             }
385 
386         } finally {
387             jcrSession.logout();
388         }
389     }
390 
391     private Method getMethod(Class theclass, String methodName) throws NoSuchMethodException {
392         Method[] declaredMethods = theclass.getDeclaredMethods();
393 
394         for (Method method : declaredMethods) {
395             if (method.getName().equals(methodName)) {
396                 return method;
397             }
398         }
399 
400         throw new NoSuchMethodException(theclass.getName() + "." + methodName + "()");
401     }
402 
403     /**
404      * @return InputStream of node type definition file
405      */
406     private InputStream getNodeTypeDefinition(String configuration) {
407 
408         InputStream xml;
409 
410         if (StringUtils.isNotEmpty(configuration)) {
411 
412             // 1: try to load the configured file from the classpath
413             xml = getClass().getResourceAsStream(configuration);
414             if (xml != null) {
415                 log.info("Custom node types registered using {}", configuration);
416                 return xml;
417             }
418 
419             // 2: try to load it from the file system
420             File nodeTypeDefinition = new File(fileSystemHelper.getAbsoluteFileSystemPath(configuration));
421             if (nodeTypeDefinition.exists()) {
422                 try {
423                     return new FileInputStream(nodeTypeDefinition);
424                 } catch (FileNotFoundException e) {
425                     // should never happen
426                     log.error("File not found: {}", configuration);
427                 }
428             }
429 
430             // 3: defaults to standard nodetypes
431             log.error("Unable to find node type definition: {} for repository {}", configuration, this.repositoryMapping.getName());
432         }
433 
434         // initialize default magnolia nodetypes
435         xml = getClass().getResourceAsStream(MGNL_NODETYPES);
436 
437         return xml;
438     }
439 
440     /**
441      * WORKAROUND for tomcat 5.0/jdk 1.5 problem tomcat\common\endorsed contains an xml-apis.jar needed by tomcat and
442      * loaded before all xmsl stuff present in the jdk (1.4 naming problem). In the xml-apis.jar file the
443      * TransformerFactoryImpl is set to "org.apache.xalan.processor.TransformerFactoryImpl" instead of
444      * "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl". solution: remove the file xml-apis.jar
445      * from the directory OR manually change the javax.xml.transform.TransformerFactory system property
446      */
447     protected void checkXmlSettings() {
448         if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_5)
449                 && "org.apache.xalan.processor.TransformerFactoryImpl".equals(System
450                 .getProperty("javax.xml.transform.TransformerFactory"))) {
451 
452             String transformerClass = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
453 
454             try {
455                 Class.forName(transformerClass);
456 
457                 System.setProperty("javax.xml.transform.TransformerFactory", transformerClass);
458 
459                 log.info("Java 1.5 detected, setting system property \"javax.xml.transform.TransformerFactory\" to \"{}\"", transformerClass);
460             } catch (Throwable e) {
461                 // not in the classpath. We can't assume which one to use, so just go on
462             }
463         }
464     }
465 
466     /**
467      * Checks if all workspaces are present according to the repository mapping, creates any missing workspace.
468      */
469     private void validateWorkspaces() throws RepositoryException {
470         Iterator<String> configuredNames = repositoryMapping.getWorkspaces().iterator();
471         while (configuredNames.hasNext()) {
472             registerWorkspace(configuredNames.next());
473         }
474     }
475 
476     /**
477      * @see info.magnolia.repository.Provider#registerWorkspace(java.lang.String)
478      */
479     @Override
480     public boolean registerWorkspace(String workspaceName) throws RepositoryException {
481         // check if workspace already exists
482         SimpleCredentials credentials = new SimpleCredentials(
483                 ContentRepository.REPOSITORY_USER,
484                 ContentRepository.REPOSITORY_PSWD.toCharArray());
485         Session jcrSession = this.repository.login(credentials);
486 
487         try {
488             WorkspaceImpl defaultWorkspace = (WorkspaceImpl) jcrSession.getWorkspace();
489             String[] workspaceNames = defaultWorkspace.getAccessibleWorkspaceNames();
490 
491             boolean alreadyExists = ArrayUtils.contains(workspaceNames, workspaceName);
492             if (!alreadyExists) {
493                 defaultWorkspace.createWorkspace(workspaceName);
494             }
495             jcrSession.logout();
496 
497             return !alreadyExists;
498         } catch (ClassCastException e) {
499             // this could happen if the repository provider does not have proper Shared API for the
500             // application server like at the moment in Jackrabbit
501             log.debug("Unable to register workspace, will continue", e);
502         } catch (Throwable t) {
503             log.error("Unable to register workspace, will continue", t);
504         }
505         return false;
506     }
507 
508     @Override
509     public Session getSystemSession(String workspaceName) throws RepositoryException {
510 
511         // FIXME: stop using SystemProperty, but IoC is not ready yet when this is called (config loader calls repo.init() which results in authentication calls being made and this method being invoked
512 
513         String user = SystemProperty.getProperty("magnolia.connection.jcr.admin.userId", SystemProperty.getProperty("magnolia.connection.jcr.userId", "admin"));
514         String pwd = SystemProperty.getProperty("magnolia.connection.jcr.admin.password", SystemProperty.getProperty("magnolia.connection.jcr.password", "admin"));
515         return this.repository.login(new SimpleCredentials(user, pwd.toCharArray()), workspaceName);
516     }
517 
518 }