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