View Javadoc

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