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   * @author Sameer Charles
84   * @author Fabrizio Giustina
85   * @version $Id: ProviderImpl.java 34499 2010-05-17 08:38:02Z had $
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"; //$NON-NLS-1$
102 
103     private static final String REPOSITORY_HOME_KEY = "repositoryHome"; //$NON-NLS-1$
104 
105     private static final String CONTEXT_FACTORY_CLASS_KEY = "contextFactoryClass"; //$NON-NLS-1$
106 
107     private static final String PROVIDER_URL_KEY = "providerURL"; //$NON-NLS-1$
108 
109     private static final String BIND_NAME_KEY = "bindName"; //$NON-NLS-1$
110 
111     private static final String MGNL_NODETYPES = "/mgnl-nodetypes/magnolia-nodetypes.xml"; //$NON-NLS-1$
112 
113     private static final String CUSTOM_NODETYPES = "customNodeTypes"; //$NON-NLS-1$
114 
115     private RepositoryMapping 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      * Find 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.RepositoryMapping)
165      */
166     public void init(RepositoryMapping repositoryMapping) throws RepositoryNotInitializedException {
167         checkXmlSettings();
168 
169         this.repositoryMapping = repositoryMapping;
170         /* connect to repository */
171         Map params = this.repositoryMapping.getParameters();
172         String configFile = (String) params.get(CONFIG_FILENAME_KEY);
173         configFile = Path.getAbsoluteFileSystemPath(configFile);
174         String repositoryHome = (String) params.get(REPOSITORY_HOME_KEY);
175         repositoryHome = getRepositoryHome(repositoryHome);
176 
177         // cleanup the path, to remove eventual ../.. and make it absolute
178         try {
179             File repoHomeFile = new File(repositoryHome);
180             repositoryHome = repoHomeFile.getCanonicalPath();
181         }
182         catch (IOException e1) {
183             // should never happen and it's not a problem at this point, just pass it to jackrabbit and see
184         }
185 
186         String clusterid = SystemProperty.getProperty(MAGNOLIA_CLUSTERID_PROPERTY);
187         if (StringUtils.isNotBlank(clusterid)) {
188             System.setProperty(JACKRABBIT_CLUSTER_ID_PROPERTY, clusterid);
189         }
190 
191         // get it back from system properties, if it has been set elsewhere
192         clusterid = System.getProperty(JACKRABBIT_CLUSTER_ID_PROPERTY);
193 
194         log.info("Loading repository at {} (config file: {}) - cluster id: \"{}\"", new Object[]{
195             repositoryHome,
196             configFile,
197             StringUtils.defaultString(clusterid, "<unset>")});
198 
199         bindName = (String) params.get(BIND_NAME_KEY);
200         jndiEnv = new Hashtable<String, Object>();
201         jndiEnv.put(Context.INITIAL_CONTEXT_FACTORY, params.get(CONTEXT_FACTORY_CLASS_KEY));
202         jndiEnv.put(Context.PROVIDER_URL, params.get(PROVIDER_URL_KEY));
203 
204         try {
205             InitialContext ctx = new InitialContext(jndiEnv);
206             // first try to find the existing object if any
207             try {
208                 this.repository = (Repository) ctx.lookup(bindName);
209             }
210             catch (NameNotFoundException ne) {
211                 log.debug("No JNDI bound Repository found with name {}, trying to initialize a new Repository", bindName);
212                 RegistryHelper.registerRepository(ctx, bindName, configFile, repositoryHome, true);
213                 this.repository = (Repository) ctx.lookup(bindName);
214             }
215             this.validateWorkspaces();
216         }
217         catch (NamingException e) {
218             log.error("Unable to initialize repository: " + e.getMessage(), e);
219             throw new RepositoryNotInitializedException(e);
220         }
221         catch (RepositoryException e) {
222             log.error("Unable to initialize repository: " + e.getMessage(), e);
223             throw new RepositoryNotInitializedException(e);
224         }
225         catch (TransformerFactoryConfigurationError e) {
226             log.error("Unable to initialize repository: " + e.getMessage(), e);
227             throw new RepositoryNotInitializedException(e);
228         }
229     }
230 
231     public void shutdownRepository() {
232         log.info("Shutting down repository bound to '{}'", bindName);
233 
234         try {
235             Context ctx = new InitialContext(jndiEnv);
236             RegistryHelper.unregisterRepository(ctx, bindName);
237         } catch (NamingException e) {
238             log.warn("Unable to shutdown repository " + bindName + ": " + e.getMessage(), e);
239         } catch (Throwable e) {
240             log.error("Failed to shutdown repository " + bindName + ": " + e.getMessage(), e);
241         }
242     }
243 
244     /**
245      * @deprecated typo - use get #getUnderlyingRepository() - since 4.0
246      */
247     public Repository getUnderlineRepository() throws RepositoryNotInitializedException {
248         return getUnderlyingRepository();
249     }
250 
251     public Repository getUnderlyingRepository() throws RepositoryNotInitializedException {
252         if (this.repository == null) {
253             throw new RepositoryNotInitializedException("Null repository"); //$NON-NLS-1$
254         }
255         return this.repository;
256     }
257 
258     /**
259      * @see info.magnolia.repository.Provider#registerNamespace(java.lang.String, java.lang.String, javax.jcr.Workspace)
260      */
261     public void registerNamespace(String namespacePrefix, String uri, Workspace workspace) throws RepositoryException {
262         try {
263             workspace.getNamespaceRegistry().getURI(namespacePrefix);
264         }
265         catch (NamespaceException e) {
266             if (log.isDebugEnabled()) {
267                 log.debug(e.getMessage());
268             }
269             log.info("Registering prefix [{}] with URI {}", namespacePrefix, uri); //$NON-NLS-1$
270             workspace.getNamespaceRegistry().registerNamespace(namespacePrefix, uri);
271         }
272     }
273 
274     /**
275      * @see info.magnolia.repository.Provider#unregisterNamespace(java.lang.String, javax.jcr.Workspace)
276      */
277     public void unregisterNamespace(String prefix, Workspace workspace) throws RepositoryException {
278         workspace.getNamespaceRegistry().unregisterNamespace(prefix);
279     }
280 
281     /**
282      * @see info.magnolia.repository.Provider#registerNodeTypes(String)
283      */
284     public void registerNodeTypes() throws RepositoryException {
285         registerNodeTypes(StringUtils.EMPTY);
286     }
287 
288     /**
289      * @see info.magnolia.repository.Provider#registerNodeTypes(java.lang.String)
290      */
291     public void registerNodeTypes(String configuration) throws RepositoryException {
292         if (StringUtils.isEmpty(configuration)) {
293             configuration = (String) 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     public void registerNodeTypes(InputStream xmlStream) throws RepositoryException {
304         SimpleCredentials credentials = new SimpleCredentials(
305             ContentRepository.REPOSITORY_USER,
306             ContentRepository.REPOSITORY_PSWD.toCharArray());
307         Session jcrSession = this.repository.login(credentials);
308 
309         try {
310 
311             Workspace workspace = jcrSession.getWorkspace();
312 
313             // should never happen
314             if (xmlStream == null) {
315                 throw new MissingNodetypesException();
316             }
317 
318             // Use Objects so that it works both with jackrabbit 1.x (NodeTypeDef) and jackrabbit 2
319             // (QNodeTypeDefinition)
320             Object[] types;
321 
322             try {
323                 types = (Object[]) NodeTypeReader.class.getMethod("read", new Class[]{InputStream.class}).invoke(
324                     null,
325                     new Object[]{xmlStream});
326             }
327             catch (Exception e) {
328                 throw new RepositoryException(e.getMessage(), e);
329             }
330             finally {
331                 IOUtils.closeQuietly(xmlStream);
332             }
333 
334             NodeTypeManager ntMgr = workspace.getNodeTypeManager();
335             NodeTypeRegistry ntReg;
336             try {
337                 ntReg = ((NodeTypeManagerImpl) ntMgr).getNodeTypeRegistry();
338             }
339             catch (ClassCastException e) {
340                 // this could happen if the repository provider does not have proper Shared API for the
341                 // application server like at the moment in Jackrabbit
342                 log.debug("Failed to get NodeTypeRegistry: ", e);
343                 return;
344             }
345 
346             for (int j = 0; j < types.length; j++) {
347                 Object def = types[j];
348 
349                 Name ntname;
350                 try {
351                     ntname = (Name) PropertyUtils.getProperty(def, "name");
352                 }
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                 }
365                 catch (IllegalArgumentException e)
366                 {
367                     throw new RepositoryException(e.getMessage(), e);
368                 }
369                 catch (IllegalAccessException e)
370                 {
371                     throw new RepositoryException(e.getMessage(), e);
372                 }
373                 catch (SecurityException e)
374                 {
375                     throw new RepositoryException(e.getMessage(), e);
376                 }
377                 catch (NoSuchMethodException e)
378                 {
379                     throw new RepositoryException(e.getMessage(), e);
380                 }
381                 catch (InvocationTargetException ite)
382                 {
383                     if (ite.getTargetException() instanceof NoSuchNodeTypeException)
384                     {
385                         log.info("Registering nodetype {} on repository {}", ntname, repositoryMapping.getName()); //$NON-NLS-1$
386 
387                         try
388                         {
389                             // reflection for jackrabbit 1+2 compatibility
390                             getMethod(NodeTypeRegistry.class, "registerNodeType").invoke(ntReg, new Object[]{def });
391                         }
392                         catch (Exception e)
393                         {
394                             throw new RepositoryException(e.getMessage(), e);
395                         }
396                     }
397                 }
398             }
399 
400         }
401         finally {
402             jcrSession.logout();
403         }
404     }
405 
406     private Method getMethod(Class theclass, String methodName) throws NoSuchMethodException {
407         Method[] declaredMethods = theclass.getDeclaredMethods();
408 
409         for (Method method : declaredMethods) {
410             if (method.getName().equals(methodName)) {
411                 return method;
412             }
413         }
414 
415         throw new NoSuchMethodException(theclass.getName() + "." + methodName + "()");
416     }
417 
418     /**
419      * @param configuration
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: {}", xml);
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(1.5f)
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     public boolean registerWorkspace(String workspaceName) throws RepositoryException {
498         // check if workspace already exists
499         SimpleCredentials credentials = new SimpleCredentials(
500             ContentRepository.REPOSITORY_USER,
501             ContentRepository.REPOSITORY_PSWD.toCharArray());
502         Session jcrSession = this.repository.login(credentials);
503 
504         try {
505             WorkspaceImpl defaultWorkspace = (WorkspaceImpl) jcrSession.getWorkspace();
506             String[] workspaceNames = defaultWorkspace.getAccessibleWorkspaceNames();
507 
508             boolean alreadyExists = ArrayUtils.contains(workspaceNames, workspaceName);
509             if (!alreadyExists) {
510                 defaultWorkspace.createWorkspace(workspaceName);
511             }
512             jcrSession.logout();
513 
514             return !alreadyExists;
515         } catch (ClassCastException e) {
516             // this could happen if the repository provider does not have proper Shared API for the
517             // application server like at the moment in Jackrabbit
518             log.debug("Unable to register workspace, will continue", e);
519         } catch (Throwable t) {
520             log.error("Unable to register workspace, will continue", t);
521         }
522         return false;
523     }
524 
525 }