View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 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.RepositoryMapping;
41  import info.magnolia.repository.RepositoryNotInitializedException;
42  
43  import org.apache.commons.beanutils.PropertyUtils;
44  import org.apache.commons.io.IOUtils;
45  import org.apache.commons.lang.ArrayUtils;
46  import org.apache.commons.lang.StringUtils;
47  import org.apache.commons.lang.SystemUtils;
48  import org.apache.jackrabbit.core.WorkspaceImpl;
49  import org.apache.jackrabbit.core.jndi.RegistryHelper;
50  import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
51  import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
52  import org.apache.jackrabbit.core.nodetype.xml.NodeTypeReader;
53  import org.apache.jackrabbit.spi.Name;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
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  import java.io.File;
71  import java.io.FileInputStream;
72  import java.io.FileNotFoundException;
73  import java.io.IOException;
74  import java.io.InputStream;
75  import java.lang.reflect.InvocationTargetException;
76  import java.lang.reflect.Method;
77  import java.util.Hashtable;
78  import java.util.Iterator;
79  import java.util.Map;
80  
81  
82  /**
83   * Provider implementation for Apache JackRabbit JCR repository.
84   * @author Sameer Charles
85   * @author Fabrizio Giustina
86   * @version $Id: ProviderImpl.java 37744 2010-10-07 12:45:24Z pbaerfuss $
87   */
88  public class ProviderImpl implements Provider {
89  
90      /**
91       * Magnolia property (entry in magnolia.properties) with the cluster id, which will be passed to jackrabbit.
92       */
93      private static final String MAGNOLIA_CLUSTERID_PROPERTY = "magnolia.clusterid";
94  
95      /**
96       * Jackrabbit system property for cluster node id.
97       */
98      private static final String JACKRABBIT_CLUSTER_ID_PROPERTY = "org.apache.jackrabbit.core.cluster.node_id";
99  
100     private static final Logger log = LoggerFactory.getLogger(ProviderImpl.class);
101 
102     private static final String CONFIG_FILENAME_KEY = "configFile"; //$NON-NLS-1$
103 
104     private static final String REPOSITORY_HOME_KEY = "repositoryHome"; //$NON-NLS-1$
105 
106     private static final String CONTEXT_FACTORY_CLASS_KEY = "contextFactoryClass"; //$NON-NLS-1$
107 
108     private static final String PROVIDER_URL_KEY = "providerURL"; //$NON-NLS-1$
109 
110     private static final String BIND_NAME_KEY = "bindName"; //$NON-NLS-1$
111 
112     private static final String MGNL_NODETYPES = "/mgnl-nodetypes/magnolia-nodetypes.xml"; //$NON-NLS-1$
113 
114     private static final String CUSTOM_NODETYPES = "customNodeTypes"; //$NON-NLS-1$
115 
116     private RepositoryMapping repositoryMapping;
117 
118     private Repository repository;
119 
120     private String bindName;
121 
122     private Hashtable<String, Object> jndiEnv;
123 
124     private static final String REPO_HOME_PREFIX = "${repository.home}";
125 
126     private static final int REPO_HOME_SUFIX_LEN = REPO_HOME_PREFIX.length();
127 
128     private static final String sysRepositoryHome = System.getProperty("repository.home");
129 
130     private static final String sysRepositoryHomes = System.getProperty("repository.homes");
131 
132     /**
133      * Finds the physical path to the repository folder.
134      * @param repositoryHome the property set in the repository.xml file
135      * @return the full path resolved to the repository dir
136      */
137     private String getRepositoryHome(final String repositoryHome) {
138         boolean relocate = false;
139         String tmp = repositoryHome;
140         if (repositoryHome.startsWith(REPOSITORY_HOME_KEY)) {
141             tmp = repositoryHome.substring(REPO_HOME_SUFIX_LEN);
142             relocate = true;
143         }
144         /*
145          * Resolve if the path started with the suffix
146          */
147         if (sysRepositoryHome != null && relocate) {
148             return sysRepositoryHome + File.separator + tmp;
149         }
150 
151         /*
152          * This is apply to all repositories if the java property is set
153          */
154         if (sysRepositoryHomes != null) {
155             return sysRepositoryHomes + File.separator + tmp;
156         }
157 
158         /*
159          * Return the same value as before if neither of the above applied
160          */
161         return Path.getAbsoluteFileSystemPath(tmp);
162     }
163 
164     /**
165      * @see info.magnolia.repository.Provider#init(info.magnolia.repository.RepositoryMapping)
166      */
167     public void init(RepositoryMapping 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     public void shutdownRepository() {
233         log.info("Shutting down repository bound to '{}'", bindName);
234 
235         try {
236             Context ctx = new InitialContext(jndiEnv);
237             RegistryHelper.unregisterRepository(ctx, bindName);
238         } catch (NamingException e) {
239             log.warn("Unable to shutdown repository " + bindName + ": " + e.getMessage(), e);
240         } catch (Throwable e) {
241             log.error("Failed to shutdown repository " + bindName + ": " + e.getMessage(), e);
242         }
243     }
244 
245     /**
246      * @deprecated typo - use get #getUnderlyingRepository() - since 4.0
247      */
248     public Repository getUnderlineRepository() throws RepositoryNotInitializedException {
249         return getUnderlyingRepository();
250     }
251 
252     public Repository getUnderlyingRepository() throws RepositoryNotInitializedException {
253         if (this.repository == null) {
254             throw new RepositoryNotInitializedException("Null repository"); //$NON-NLS-1$
255         }
256         return this.repository;
257     }
258 
259     /**
260      * @see info.magnolia.repository.Provider#registerNamespace(java.lang.String, java.lang.String, javax.jcr.Workspace)
261      */
262     public void registerNamespace(String namespacePrefix, String uri, Workspace workspace) throws RepositoryException {
263         try {
264             workspace.getNamespaceRegistry().getURI(namespacePrefix);
265         }
266         catch (NamespaceException e) {
267             if (log.isDebugEnabled()) {
268                 log.debug(e.getMessage());
269             }
270             log.info("Registering prefix [{}] with URI {}", namespacePrefix, uri); //$NON-NLS-1$
271             workspace.getNamespaceRegistry().registerNamespace(namespacePrefix, uri);
272         }
273     }
274 
275     /**
276      * @see info.magnolia.repository.Provider#unregisterNamespace(java.lang.String, javax.jcr.Workspace)
277      */
278     public void unregisterNamespace(String prefix, Workspace workspace) throws RepositoryException {
279         workspace.getNamespaceRegistry().unregisterNamespace(prefix);
280     }
281 
282     /**
283      * @see info.magnolia.repository.Provider#registerNodeTypes(String)
284      */
285     public void registerNodeTypes() throws RepositoryException {
286         registerNodeTypes(StringUtils.EMPTY);
287     }
288 
289     /**
290      * @see info.magnolia.repository.Provider#registerNodeTypes(java.lang.String)
291      */
292     public void registerNodeTypes(String configuration) throws RepositoryException {
293         if (StringUtils.isEmpty(configuration)) {
294             configuration = (String) this.repositoryMapping.getParameters().get(CUSTOM_NODETYPES);
295         }
296 
297         InputStream xml = getNodeTypeDefinition(configuration);
298         this.registerNodeTypes(xml);
299     }
300 
301     /**
302      * @see info.magnolia.repository.Provider#registerNodeTypes(java.io.InputStream)
303      */
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()); //$NON-NLS-1$
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      * @param configuration
421      * @return InputStream of node type definition file
422      */
423     private InputStream getNodeTypeDefinition(String configuration) {
424 
425         InputStream xml;
426 
427         if (StringUtils.isNotEmpty(configuration)) {
428 
429             // 1: try to load the configured file from the classpath
430             xml = getClass().getResourceAsStream(configuration);
431             if (xml != null) {
432                 log.info("Custom node types registered using {}", configuration);
433                 return xml;
434             }
435 
436             // 2: try to load it from the file system
437             File nodeTypeDefinition = new File(Path.getAbsoluteFileSystemPath(configuration));
438             if (nodeTypeDefinition.exists()) {
439                 try {
440                     return new FileInputStream(nodeTypeDefinition);
441                 }
442                 catch (FileNotFoundException e) {
443                     // should never happen
444                     log.error("File not found: {}", xml);
445                 }
446             }
447 
448             // 3: defaults to standard nodetypes
449             log.error("Unable to find node type definition: {} for repository {}", configuration, this.repositoryMapping.getName());
450         }
451 
452         // initialize default magnolia nodetypes
453         xml = getClass().getResourceAsStream(MGNL_NODETYPES);
454 
455         return xml;
456     }
457 
458     /**
459      * WORKAROUND for tomcat 5.0/jdk 1.5 problem tomcat\common\endorsed contains an xml-apis.jar needed by tomcat and
460      * loaded before all xmsl stuff present in the jdk (1.4 naming problem). In the xml-apis.jar file the
461      * TransformerFactoryImpl is set to "org.apache.xalan.processor.TransformerFactoryImpl" instead of
462      * "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl". solution: remove the file xml-apis.jar
463      * from the directory OR manually change the javax.xml.transform.TransformerFactory system property
464      */
465     protected void checkXmlSettings() {
466         if (SystemUtils.isJavaVersionAtLeast(1.5f)
467             && "org.apache.xalan.processor.TransformerFactoryImpl".equals(System
468                 .getProperty("javax.xml.transform.TransformerFactory"))) {
469 
470             String transformerClass = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
471 
472             try {
473                 Class.forName(transformerClass);
474 
475                 System.setProperty("javax.xml.transform.TransformerFactory", transformerClass);
476 
477                 log.info("Java 1.5 detected, setting system property \"javax.xml.transform.TransformerFactory\" to \"{}\"", transformerClass);
478             }
479             catch (Throwable e) {
480                 // not in the classpath. We can't assume which one to use, so just go on
481             }
482         }
483     }
484 
485     /**
486      * Checks if all workspaces are present according to the repository mapping, creates any missing workspace.
487      */
488     private void validateWorkspaces() throws RepositoryException {
489         Iterator<String> configuredNames = repositoryMapping.getWorkspaces().iterator();
490         while (configuredNames.hasNext()) {
491             registerWorkspace(configuredNames.next());
492         }
493     }
494 
495     /**
496      * @see info.magnolia.repository.Provider#registerWorkspace(java.lang.String)
497      */
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 }