001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.web.server;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.FileOutputStream;
021import java.math.BigDecimal;
022import java.security.Principal;
023import java.util.ArrayList;
024import java.util.Calendar;
025import java.util.Collection;
026import javax.jcr.Node;
027import javax.jcr.NodeIterator;
028import javax.jcr.PathNotFoundException;
029import javax.jcr.Property;
030import javax.jcr.PropertyType;
031import javax.jcr.Repository;
032import javax.jcr.RepositoryException;
033import javax.jcr.Session;
034import javax.jcr.Value;
035import javax.jcr.nodetype.NodeType;
036import javax.jcr.nodetype.NodeTypeIterator;
037import javax.jcr.nodetype.NodeTypeManager;
038import javax.jcr.nodetype.PropertyDefinition;
039import javax.jcr.query.Query;
040import javax.jcr.query.QueryManager;
041import javax.jcr.query.QueryResult;
042import javax.jcr.query.Row;
043import javax.jcr.query.RowIterator;
044import javax.jcr.security.AccessControlEntry;
045import javax.jcr.security.AccessControlList;
046import javax.jcr.security.AccessControlManager;
047import javax.jcr.security.AccessControlPolicy;
048import javax.jcr.security.Privilege;
049import org.modeshape.common.logging.Logger;
050import org.modeshape.web.client.JcrService;
051import org.modeshape.web.shared.RemoteException;
052import org.modeshape.web.shared.Acl;
053import org.modeshape.web.shared.JcrNode;
054import org.modeshape.web.shared.JcrNodeType;
055import org.modeshape.web.shared.JcrPermission;
056import org.modeshape.web.shared.JcrProperty;
057import org.modeshape.web.shared.JcrRepositoryDescriptor;
058import org.modeshape.web.shared.Policy;
059import org.modeshape.web.shared.RepositoryName;
060import org.modeshape.web.shared.ResultSet;
061import com.google.gwt.user.server.rpc.RemoteServiceServlet;
062import java.io.IOException;
063import java.util.Arrays;
064import java.util.Date;
065import javax.jcr.AccessDeniedException;
066import javax.jcr.InvalidItemStateException;
067import javax.jcr.ItemExistsException;
068import javax.jcr.ReferentialIntegrityException;
069import javax.jcr.lock.LockException;
070import javax.jcr.nodetype.ConstraintViolationException;
071import javax.jcr.nodetype.NoSuchNodeTypeException;
072import javax.jcr.version.VersionException;
073import javax.servlet.ServletContext;
074import javax.servlet.http.HttpServletRequest;
075import org.modeshape.jcr.JcrI18n;
076import org.modeshape.jcr.JcrRepository;
077import org.modeshape.jcr.RepositoryStatistics;
078import org.modeshape.jcr.api.monitor.DurationMetric;
079import org.modeshape.jcr.api.monitor.History;
080import org.modeshape.jcr.api.monitor.Statistics;
081import org.modeshape.jcr.api.monitor.ValueMetric;
082import org.modeshape.jcr.api.monitor.Window;
083import org.modeshape.web.server.impl.MsDurationMetric;
084import org.modeshape.web.server.impl.MsValueMetric;
085import org.modeshape.web.server.impl.TimeUnit;
086import org.modeshape.web.shared.BackupParams;
087import org.modeshape.web.shared.RestoreParams;
088import org.modeshape.web.shared.Stats;
089
090/**
091 * The server side implementation of the RPC service.
092 */
093@SuppressWarnings( "serial" )
094public class JcrServiceImpl extends RemoteServiceServlet implements JcrService {
095
096    private final static String CONNECTOR_CLASS_PARAMETER = "connector-class";    
097    private final static String DEFAULT_CONNECTOR_CLASS = 
098            "org.modeshape.web.server.impl.ConnectorImpl";    
099    
100    private final static Logger logger = Logger.getLogger(JcrServiceImpl.class);
101    
102    
103    private Connector connector() throws RemoteException {
104        Connector connector = (Connector)getThreadLocalRequest().getSession(true).getAttribute("connector");
105        if (isUnknown(connector)) {
106            String clsName = getConnectorClassName();
107            if (isUnknown(clsName)) {
108                clsName = DEFAULT_CONNECTOR_CLASS;
109            }
110            connector = loadConnector(clsName, getServletContext());
111            getThreadLocalRequest().getSession(true).setAttribute("connector", connector);
112        }
113        return connector;
114    }
115
116    private String getConnectorClassName() {
117        return getServletContext().getInitParameter(CONNECTOR_CLASS_PARAMETER);
118    }
119    
120    @Override
121    public String getRequestedURI() {
122        String uri = (String)getThreadLocalRequest().getSession().getAttribute("initial.uri");
123        if (uri != null) {
124            logger.debug("Requested URI " + uri);
125            return uri;
126        }
127
128        uri = getThreadLocalRequest().getRequestURI();
129        String servletPath = getThreadLocalRequest().getServletPath();
130
131        String res = uri.substring(0, uri.indexOf(servletPath));
132        logger.debug("Requested URI " + uri);
133        return res;
134    }
135
136    @Override
137    public String getUserName() throws RemoteException {
138        ServletContext context = getServletContext();
139        
140        //get user's credentials from servlet context
141        String uname = (String)context.getAttribute("uname");
142        String passwd = (String)context.getAttribute("password");
143        
144        //login to the repositories
145        if (uname != null) {
146            connector().login(uname, passwd);
147        }
148        
149        //return user's name
150        String res = connector().userName();
151        return res;
152    }
153
154    @Override
155    public Collection<RepositoryName> getRepositories() throws RemoteException {
156        return connector().getRepositories();
157    }
158
159    @Override
160    public Collection<RepositoryName> findRepositories( String criteria ) throws RemoteException {
161        return connector().search(criteria);
162    }
163
164    @Override
165    public String[] getWorkspaces( String repositoryName ) throws RemoteException {
166        return connector().find(repositoryName).getWorkspaces();
167    }
168
169    @Override
170    public void login( String userName,
171                       String password ) throws RemoteException {
172        connector().login(userName, password);
173    }
174
175    @Override
176    public String logout() {
177        try {
178            connector().logout();
179        } catch (RemoteException e) {
180            //nothing to do here in case of the exception
181        } finally {
182            //clean up session
183            getThreadLocalRequest().getSession().invalidate();
184            
185            //clean up context
186            getServletContext().removeAttribute("uname");
187            getServletContext().removeAttribute("password");
188            
189            //redirect to initial page. it will trigger login form
190            return getThreadLocalRequest().getContextPath() + "/Console.html";
191        }
192    }
193    
194    @Override
195    public JcrNode node( String repository,
196                         String workspace,
197                         String path ) throws RemoteException {
198        logger.debug("Requested node in repository '" + repository + "', workspace '" + workspace + "', path '" + path + "'");
199
200        if (repository == null || workspace == null) {
201            return null;
202        }
203        try {
204            Session session = connector().find(repository).session(workspace);
205            Node n = session.getNode(path);
206
207            // convert into value object
208            JcrNode node = new JcrNode(repository, workspace, n.getName(), n.getPath(), n.getPrimaryNodeType().getName());
209            node.setMixins(mixinTypes(n));
210            node.setProperties(getProperties(repository, workspace, path, n));
211
212            node.setPropertyDefs(propertyDefs(n));
213            
214            try {
215                node.setAcl(getAcl(repository, workspace, path));
216            } catch (AccessDeniedException e) {
217                node.setAcl(null);
218            }
219
220            NodeIterator it = n.getNodes();
221            node.setChildCount(it.getSize());
222            return node;
223        } catch (RepositoryException e) {
224            logger.error(e,  JcrI18n.unexpectedException, e.getMessage());
225            throw new RemoteException(e.getMessage());
226        }
227    }
228
229    @Override
230    public Collection<JcrNode> childNodes(String repository, String workspace, String path, int index, int count) throws RemoteException {
231        if (repository == null || workspace == null) {
232            return null;
233        }
234        try {
235            Session session = connector().find(repository).session(workspace);
236            Node n = session.getNode(path);
237
238            NodeIterator it = n.getNodes();
239            
240            ArrayList<JcrNode> res = new ArrayList();
241            int i = 0;
242            while (it.hasNext()) {
243                Node child = it.nextNode();
244                if (i >= index && i < (index + count)) {
245                    res.add(new JcrNode(repository, workspace, child.getName(), child.getPath(), child.getPrimaryNodeType().getName()));
246                }
247                i++;
248            }
249            return res;
250        } catch (RepositoryException e) {
251            logger.error(e,  JcrI18n.unexpectedException, e.getMessage());
252            throw new RemoteException(e.getMessage());
253        }
254    }
255    
256    private String[] mixinTypes( Node node ) throws RepositoryException {
257        NodeType[] values = node.getMixinNodeTypes();
258        String[] res = new String[values.length];
259        for (int i = 0; i < res.length; i++) {
260            res[i] = values[i].getName();
261        }
262        return res;
263    }
264
265    /**
266     * Gets the list of properties available to the given node.
267     * 
268     * @param node the node instance.
269     * @return list of property names.
270     * @throws RepositoryException
271     */
272    private String[] propertyDefs( Node node ) throws RepositoryException {
273        ArrayList<String> list = new ArrayList<>();
274
275        NodeType primaryType = node.getPrimaryNodeType();
276        PropertyDefinition[] defs = primaryType.getPropertyDefinitions();
277
278        for (PropertyDefinition def : defs) {
279            if (!def.isProtected()) {
280                list.add(def.getName());
281            }
282        }
283
284        NodeType[] mixinType = node.getMixinNodeTypes();
285        for (NodeType type : mixinType) {
286            defs = type.getPropertyDefinitions();
287            for (PropertyDefinition def : defs) {
288                if (!def.isProtected()) {
289                    list.add(def.getName());
290                }
291            }
292        }
293
294        String[] res = new String[list.size()];
295        list.toArray(res);
296
297        return res;
298    }
299
300    private Acl getAcl( String repository,
301                        String workspace,
302                        String path ) throws RepositoryException, RemoteException {
303        Session session = connector().find(repository).session(workspace);
304
305        AccessControlManager acm = session.getAccessControlManager();
306        AccessControlList accessList = findAccessList(acm, path);
307
308        if (accessList == null) {
309            return null;
310        }
311
312        Acl acl = new Acl();
313
314        AccessControlEntry[] entries = accessList.getAccessControlEntries();
315        for (AccessControlEntry entry : entries) {
316            Policy policy = new Policy();
317            policy.setPrincipal(entry.getPrincipal().getName());
318            Privilege[] privileges = entry.getPrivileges();
319            for (Privilege privilege : privileges) {
320                policy.enable(privilege.getName());
321            }
322            acl.addPolicy(policy);
323        }
324        return acl;
325    }
326
327    /**
328     * Searches access list for the given node.
329     * 
330     * @param acm access manager
331     * @param path the path to the node
332     * @return access list representation.
333     * @throws RepositoryException
334     */
335    private AccessControlList findAccessList( AccessControlManager acm,
336                                              String path ) throws RepositoryException {
337        AccessControlPolicy[] policy = acm.getPolicies(path);
338
339        if (policy != null && policy.length > 0) {
340            return (AccessControlList)policy[0];
341        }
342
343        policy = acm.getEffectivePolicies(path);
344        if (policy != null && policy.length > 0) {
345            return (AccessControlList)policy[0];
346        }
347
348        return null;
349    }
350
351    /**
352     * Reads properties of the given node.
353     * 
354     * @param repository the repository name
355     * @param workspace the workspace name
356     * @param path the path to the node
357     * @param node the node instance
358     * @return list of node's properties.
359     * @throws RepositoryException
360     */
361    private Collection<JcrProperty> getProperties( String repository, String workspace, String path, Node node ) throws RepositoryException {
362        ArrayList<PropertyDefinition> names = new ArrayList<>();
363        
364        NodeType primaryType = node.getPrimaryNodeType();
365        PropertyDefinition[] defs = primaryType.getPropertyDefinitions();
366
367        names.addAll(Arrays.asList(defs));
368        
369        NodeType[] mixinType = node.getMixinNodeTypes();
370        for (NodeType type : mixinType) {
371            defs = type.getPropertyDefinitions();
372            names.addAll(Arrays.asList(defs));
373        }
374
375        ArrayList<JcrProperty> list = new ArrayList<>();
376        for (PropertyDefinition def :  names) {
377            
378            String name = def.getName();
379            String type = PropertyType.nameFromValue(def.getRequiredType());
380            
381            Property p = null;
382            try {
383                p = node.getProperty(def.getName());
384            } catch (PathNotFoundException e) {
385            }
386
387            String display = values(def, p);
388            String value = def.isMultiple() ? multiValue(p)
389                    : singleValue(p, def, repository, workspace, path);
390            list.add(new JcrProperty(name, type, value, display));
391            
392        }
393        return list;
394    }    
395
396    private String singleValue(Property p, PropertyDefinition def, 
397            String repository, String workspace, String path) throws RepositoryException {
398        switch (def.getRequiredType()) {
399            case PropertyType.BINARY:
400                HttpServletRequest request = this.getThreadLocalRequest();
401                String context = request.getContextPath();
402                return String.format("%s/binary/node?repository=%s&workspace=%s&path=%s&property=%s",
403                        context, repository, workspace, path, def.getName());
404            default:
405                return p == null ? "N/A" : p.getValue() != null ? p.getValue().getString() : "N/A";
406        }
407    }
408    
409    private String multiValue(Property p) throws RepositoryException {
410        if (p == null) {
411            return "";
412        }
413        
414        Value[] values = p.getValues();
415
416        if (values == null || values.length == 0) {
417            return "";
418        }
419
420        if (values.length == 1) {
421            return values[0].getString();
422        }
423
424        String s = values[0].getString();
425        for (int i = 1; i < values.length; i++) {
426            s += "," + values[i].getString();
427        }
428
429        return s;
430    }
431    
432    /**
433     * Displays property value as string
434     * 
435     * @param pd the property definition
436     * @param p the property to display
437     * @return property value as text string
438     * @throws RepositoryException
439     */
440    private String values( PropertyDefinition pd, Property p ) throws RepositoryException {
441        if (p == null) {
442            return "N/A";
443        }
444        
445        if (pd.getRequiredType() == PropertyType.BINARY) {
446            return "BINARY";
447        }
448        
449        if (!p.isMultiple()) {
450            return p.getString();
451        }
452
453        return multiValue(p);
454    }
455
456    @Override
457    public JcrRepositoryDescriptor repositoryInfo( String repository ) throws RemoteException {
458        JcrRepositoryDescriptor desc = new JcrRepositoryDescriptor();
459
460        try {
461            Repository repo = connector().find(repository).repository();
462            String keys[] = repo.getDescriptorKeys();
463            for (int i = 0; i < keys.length; i++) {
464                Value value = repo.getDescriptorValue(keys[i]);
465                desc.add(keys[i], value != null ? value.getString() : "N/A");
466            }
467        } catch (RemoteException | IllegalStateException | RepositoryException e) {
468            throw new RemoteException(e);
469        }
470        return desc;
471    }
472
473    @Override
474    public ResultSet query( String repository,
475                            String workspace,
476                            String text,
477                            String lang ) throws RemoteException {
478        ResultSet rs = new ResultSet();
479        try {
480            Session session = connector().find(repository).session(workspace);
481
482            QueryManager qm = session.getWorkspace().getQueryManager();
483            Query q = qm.createQuery(text, lang);
484
485            QueryResult qr = q.execute();
486
487            rs.setColumnNames(qr.getColumnNames());
488            ArrayList<String[]> rows = new ArrayList<>();
489            RowIterator it = qr.getRows();
490            while (it.hasNext()) {
491                Row row = it.nextRow();
492                String[] list = new String[qr.getColumnNames().length];
493
494                for (int i = 0; i < qr.getColumnNames().length; i++) {
495                    Value v = row.getValue(qr.getColumnNames()[i]);
496                    list[i] = v != null ? v.getString() : "null";
497                }
498
499                rows.add(list);
500            }
501
502            rs.setRows(rows);
503            logger.debug("Query result: " + rs.getRows().size());
504            return rs;
505        } catch (RemoteException | RepositoryException | IllegalStateException e) {
506            throw new RemoteException(e);
507        }
508    }
509
510    @Override
511    public String[] supportedQueryLanguages( String repository,
512                                             String workspace ) throws RemoteException {
513        try {
514            Session session = connector().find(repository).session(workspace);
515            QueryManager qm = session.getWorkspace().getQueryManager();
516            return qm.getSupportedQueryLanguages();
517        } catch (RepositoryException e) {
518            throw new RemoteException(e.getMessage());
519        }
520    }
521
522    @Override
523    public JcrNode addNode( String repository,
524                         String workspace,
525                         String path,
526                         String name,
527                         String primaryType ) throws RemoteException {
528        Session session = connector().find(repository).session(workspace);
529        try {
530            Node parent = (Node)session.getItem(path);
531            Node n = parent.addNode(name, primaryType);
532            JcrNode node = new JcrNode(repository, workspace, n.getName(), n.getPath(), n.getPrimaryNodeType().getName());
533            node.setMixins(mixinTypes(n));
534            node.setProperties(getProperties(repository, workspace, path, n));
535            node.setPropertyDefs(propertyDefs(n));
536            return node;
537        } catch (RepositoryException e) {
538            logger.debug("Could not add node: " + e.getMessage());
539            throw new RemoteException(e.getMessage());
540        }
541    }
542
543    @Override
544    public void removeNode( String repository,
545                            String workspace,
546                            String path ) throws RemoteException {
547        Session session = connector().find(repository).session(workspace);
548        try {
549            Node node = (Node)session.getItem(path);
550            node.remove();
551        } catch (PathNotFoundException e) {
552            logger.debug(e.getLocalizedMessage());
553        } catch (RepositoryException e) {
554            throw new RemoteException(e.getMessage());
555        }
556    }
557
558    @Override
559    public void addMixin( String repository,
560                          String workspace,
561                          String path,
562                          String mixin ) throws RemoteException {
563        Session session = connector().find(repository).session(workspace);
564        try {
565            Node node = (Node)session.getItem(path);
566            node.addMixin(mixin);
567        } catch (PathNotFoundException e) {
568            logger.debug(e.getLocalizedMessage());
569        } catch (RepositoryException e) {
570            throw new RemoteException(e.getMessage());
571        }
572    }
573
574    @Override
575    public void removeMixin( String repository,
576                             String workspace,
577                             String path,
578                             String mixin ) throws RemoteException {
579        Session session = connector().find(repository).session(workspace);
580        try {
581            Node node = (Node)session.getItem(path);
582            node.removeMixin(mixin);
583        } catch (PathNotFoundException e) {
584            logger.debug(e.getLocalizedMessage());
585        } catch (RepositoryException e) {
586            throw new RemoteException(e.getMessage());
587        }
588    }
589
590    @Override
591    public void setProperty(JcrNode node, String name, String value) throws RemoteException {
592        Session session = connector().find(node.getRepository()).session(node.getWorkspace());
593        try {
594            Node n = session.getNode(node.getPath());
595            n.setProperty(name, value);
596        } catch (Exception e) {
597            throw new RemoteException(e.getMessage());
598        }
599    }
600
601    @Override
602    public void setProperty(JcrNode node, String name, Boolean value) throws RemoteException {
603        Session session = connector().find(node.getRepository()).session(node.getWorkspace());
604        try {
605            Node n = session.getNode(node.getPath());
606            n.setProperty(name, value);
607        } catch (Exception e) {
608            throw new RemoteException(e.getMessage());
609        }
610    }
611
612    @Override
613    public void setProperty(JcrNode node, String name, Date value) throws RemoteException {
614        Session session = connector().find(node.getRepository()).session(node.getWorkspace());
615        try {
616            Node n = session.getNode(node.getPath());
617            Calendar calendar = Calendar.getInstance();
618            calendar.setTime(value);
619            n.setProperty(name, calendar);
620        } catch (Exception e) {
621            throw new RemoteException(e.getMessage());
622        }
623    }
624    
625    public void setProperty( String repository,
626                             String workspace,
627                             String path,
628                             String name,
629                             Object value ) throws RemoteException {
630        Session session = connector().find(repository).session(workspace);
631        try {
632            Node node = (Node)session.getItem(path);
633            switch (type(node, name)) {
634                case PropertyType.BOOLEAN:
635                    node.setProperty(name, (Boolean)value);
636                    break;
637                case PropertyType.DATE:
638                    node.setProperty(name, (Calendar)value);
639                    break;
640                case PropertyType.DECIMAL:
641                    node.setProperty(name, BigDecimal.valueOf(Double.parseDouble((String)value)));
642                    break;
643                case PropertyType.DOUBLE:
644                    node.setProperty(name, Double.parseDouble((String)value));
645                    break;
646                case PropertyType.LONG:
647                    node.setProperty(name, Long.parseLong((String)value));
648                    break;
649                case PropertyType.NAME:
650                    node.setProperty(name, (String)value);
651                    break;
652                case PropertyType.PATH:
653                    node.setProperty(name, (String)value);
654                    break;
655                case PropertyType.STRING:
656                    node.setProperty(name, (String)value);
657                    break;
658                case PropertyType.URI:
659                    node.setProperty(name, (String)value);
660                    break;
661
662            }
663        } catch (PathNotFoundException e) {
664            logger.debug(e.getLocalizedMessage());
665        } catch (Exception e) {
666            logger.debug("Unexpected error", e);
667            throw new RemoteException(e.getMessage());
668        }
669    }
670
671    private int type( Node node,
672                      String propertyName ) throws RepositoryException {
673        PropertyDefinition[] defs = node.getPrimaryNodeType().getPropertyDefinitions();
674        for (PropertyDefinition def : defs) {
675            if (def.getName().equals(propertyName)) {
676                return def.getRequiredType();
677            }
678        }
679
680        NodeType[] mixins = node.getMixinNodeTypes();
681        for (NodeType type : mixins) {
682            defs = type.getPropertyDefinitions();
683            for (PropertyDefinition def : defs) {
684                if (def.getName().equals(propertyName)) {
685                    return def.getRequiredType();
686                }
687            }
688        }
689
690        return -1;
691    }
692
693    @Override
694    public void addAccessList( String repository,
695                               String workspace,
696                               String path,
697                               String principal ) throws RemoteException {
698        Session session = connector().find(repository).session(workspace);
699        try {
700            AccessControlManager acm = session.getAccessControlManager();
701            Privilege allPermissions = acm.privilegeFromName(Privilege.JCR_ALL);
702
703            AccessControlList acl = (AccessControlList)acm.getApplicablePolicies(path).nextAccessControlPolicy();
704            acl.addAccessControlEntry(new SimplePrincipal(principal), new Privilege[] {allPermissions});
705            acm.setPolicy(path, acl);
706            // session.save();
707        } catch (PathNotFoundException e) {
708            logger.debug(e.getLocalizedMessage());
709        } catch (RepositoryException e) {
710            throw new RemoteException(e.getMessage());
711        }
712    }
713
714    @Override
715    public void removeAccessList( String repository,
716                               String workspace,
717                               String path,
718                               String principal ) throws RemoteException {
719        Session session = connector().find(repository).session(workspace);
720        try {
721            AccessControlManager acm = session.getAccessControlManager();
722            AccessControlList acl = this.findAccessList(acm, path);
723
724            AccessControlEntry entry = pick(acl, principal);
725            acl.removeAccessControlEntry(entry);
726            acm.setPolicy(path, acl);
727            // session.save();
728        } catch (PathNotFoundException e) {
729            logger.debug(e.getLocalizedMessage());
730        } catch (RepositoryException e) {
731            throw new RemoteException(e.getMessage());
732        }
733    }
734    
735    @Override
736    public String[] getPrimaryTypes( String repository,
737                                     String workspace, String superType,
738                                     boolean allowAbstract ) throws RemoteException {
739        Session session = connector().find(repository).session(workspace);
740        ArrayList<String> list = new ArrayList<>();
741        try {
742            NodeTypeManager mgr = session.getWorkspace().getNodeTypeManager();
743            NodeTypeIterator it = mgr.getPrimaryNodeTypes();
744            while (it.hasNext()) {
745                NodeType nodeType = it.nextNodeType();
746                if (nodeType.isAbstract() && !allowAbstract) {
747                    continue;
748                }
749                if (superType != null 
750                        && !isInset(superType, nodeType.getDeclaredSupertypeNames())) {
751                    continue;
752                }
753                list.add(nodeType.getName());
754            }
755            String[] res = new String[list.size()];
756            list.toArray(res);
757            return res;
758        } catch (RepositoryException e) {
759            throw new RemoteException(e.getMessage());
760        }
761    }
762
763    private boolean isInset(String name, String[] list) {
764        for (int i = 0; i < list.length; i++) {
765            if (name.equals(list[i])) return true;
766        }
767        return false;
768    }
769    
770    @Override
771    public String[] getMixinTypes( String repository,
772                                   String workspace,
773                                   boolean allowAbstract ) throws RemoteException {
774        Session session = connector().find(repository).session(workspace);
775        ArrayList<String> list = new ArrayList<>();
776        try {
777            NodeTypeManager mgr = session.getWorkspace().getNodeTypeManager();
778            NodeTypeIterator it = mgr.getMixinNodeTypes();
779            while (it.hasNext()) {
780                NodeType nodeType = it.nextNodeType();
781                if (!nodeType.isAbstract() || allowAbstract) {
782                    list.add(nodeType.getName());
783                }
784            }
785            String[] res = new String[list.size()];
786            list.toArray(res);
787            return res;
788        } catch (RepositoryException e) {
789            throw new RemoteException(e.getMessage());
790        }
791    }
792
793    @Override
794    public void save( String repository,
795                      String workspace ) throws RemoteException {
796        Session session = connector().find(repository).session(workspace);
797        try {
798            session.save();
799        } catch (AccessDeniedException ex) {
800            throw new RemoteException("Access violation: " + ex.getMessage());
801        } catch (ItemExistsException ex) {
802            throw new RemoteException("Item exist: " + ex.getMessage());
803        } catch (ReferentialIntegrityException ex) {
804            throw new RemoteException("Referential integrity violation: " + ex.getMessage());
805        } catch (ConstraintViolationException ex) {
806            throw new RemoteException("Constraint violation: " + ex.getMessage());
807        } catch (InvalidItemStateException ex) {
808            throw new RemoteException("Invalid item state: " + ex.getMessage());
809        } catch (VersionException ex) {
810            throw new RemoteException("version error: " + ex.getMessage());
811        } catch (LockException ex) {
812            throw new RemoteException("Lock violation: " + ex.getMessage());
813        } catch (NoSuchNodeTypeException ex) {
814            throw new RemoteException("Node type problem: " + ex.getMessage());
815        } catch (RepositoryException ex) {
816            throw new RemoteException("Generic error: " + ex.getMessage());
817        }
818    }
819
820    @Override
821    public void updateAccessList( String repository,
822                                  String workspace,
823                                  String path,
824                                  String principal,
825                                  JcrPermission permission,
826                                  boolean enabled ) throws RemoteException {
827        Session session = connector().find(repository).session(workspace);
828        try {
829            AccessControlManager acm = session.getAccessControlManager();
830            AccessControlList acl = this.findAccessList(acm, path);
831
832            AccessControlEntry entry = pick(acl, principal);
833            acl.removeAccessControlEntry(entry);
834
835            Privilege[] privs = enabled ? includePrivilege(acm, entry.getPrivileges(), permission) : excludePrivilege(entry.getPrivileges(),
836                                                                                                                      permission);
837            acl.addAccessControlEntry(entry.getPrincipal(), privs);
838            acm.setPolicy(path, acl);
839        } catch (Exception e) {
840            throw new RemoteException(e.getMessage());
841        }
842    }
843
844    /**
845     * Picks access entry for the given principal.
846     * 
847     * @param acl
848     * @param principal
849     * @return the ACL entry
850     * @throws RepositoryException
851     */
852    private AccessControlEntry pick( AccessControlList acl,
853                                     String principal ) throws RepositoryException {
854        for (AccessControlEntry entry : acl.getAccessControlEntries()) {
855            if (entry.getPrincipal().getName().equals(principal)) {
856                return entry;
857            }
858        }
859        return null;
860    }
861
862    /**
863     * Excludes given privilege.
864     * 
865     * @param privileges
866     * @param permission
867     * @return the privileges
868     */
869    private Privilege[] excludePrivilege( Privilege[] privileges,
870                                          JcrPermission permission ) {
871        ArrayList<Privilege> list = new ArrayList<>();
872
873        for (Privilege privilege : privileges) {
874            if (!privilege.getName().equalsIgnoreCase(permission.getName())) {
875                list.add(privilege);
876            }
877        }
878
879        Privilege[] res = new Privilege[list.size()];
880        list.toArray(res);
881        return res;
882    }
883
884    /**
885     * Includes given privilege.
886     * 
887     * @param acm the access control manager
888     * @param privileges
889     * @param permission
890     * @return the privileges
891     * @throws RepositoryException
892     */
893    private Privilege[] includePrivilege( AccessControlManager acm,
894                                          Privilege[] privileges,
895                                          JcrPermission permission ) throws RepositoryException {
896        ArrayList<Privilege> list = new ArrayList<>();
897
898        for (Privilege privilege : privileges) {
899            if (!privilege.getName().equalsIgnoreCase(permission.getName())) {
900                list.add(privilege);
901            }
902        }
903
904        list.add(acm.privilegeFromName(permission.getJcrName()));
905        Privilege[] res = new Privilege[list.size()];
906        list.toArray(res);
907        return res;
908    }
909
910    @Override
911    public Collection<JcrNodeType> nodeTypes( String repository,
912                                              String workspace ) throws RemoteException {
913        Session session = connector().find(repository).session(workspace);
914        ArrayList<JcrNodeType> list = new ArrayList<>();
915        try {
916            NodeTypeManager mgr = session.getWorkspace().getNodeTypeManager();
917
918            NodeTypeIterator it = mgr.getAllNodeTypes();
919            while (it.hasNext()) {
920                NodeType type = it.nextNodeType();
921                JcrNodeType jcrType = new JcrNodeType();
922                jcrType.setName(type.getName());
923                jcrType.setAbstract(type.isAbstract());
924                jcrType.setPrimary(!type.isMixin());
925                jcrType.setMixin(type.isMixin());
926                list.add(jcrType);
927            }
928        } catch (Exception e) {
929            throw new RemoteException(e.getMessage());
930        }
931        return list;
932    }
933
934    @Override
935    public void renameNode( String repository,
936                            String workspace,
937                            String path,
938                            String name ) throws RemoteException {
939        Session session = connector().find(repository).session(workspace);
940        try {
941            Node node = session.getNode(path);
942            session.move(path, node.getParent().getPath() + "/" + name);
943        } catch (Exception e) {
944            throw new RemoteException(e.getMessage());
945        }
946    }
947
948    @Override
949    public void backup( String repository,
950                        String name,
951                        BackupParams params) throws RemoteException {
952        connector().find(repository).backup(name, params);
953    }
954
955    @Override
956    public void restore( String repository,
957                         String name,
958                         RestoreParams params) throws RemoteException {
959        connector().find(repository).restore(name, params);
960    }
961
962    @Override
963    public void export( String repository,
964                        String workspace,
965                        String path,
966                        String location,
967                        boolean skipBinary,
968                        boolean noRecurse ) throws RemoteException {
969        File file = new File(location);
970        try {
971            FileOutputStream fout = new FileOutputStream(file);
972            connector().find(repository).session(workspace).exportSystemView(path, fout, skipBinary, noRecurse);
973        } catch (RemoteException | IOException | RepositoryException e) {
974            throw new RemoteException(e);
975        }
976    }
977
978    @Override
979    public void importXML( String repository,
980                           String workspace,
981                           String path,
982                           String location,
983                           int option ) throws RemoteException {
984        File file = new File(location);
985        try {
986            FileInputStream fin = new FileInputStream(file);
987            connector().find(repository).session(workspace).getWorkspace().importXML(path, fin, option);
988        } catch (RemoteException | IOException | RepositoryException e) {
989            throw new RemoteException();
990        }
991    }
992
993    @Override
994    public void refreshSession(String repository, String workspace, 
995        boolean keepChanges) throws RemoteException {
996        try {
997            connector().find(repository).session(workspace).refresh(keepChanges);
998        } catch (RemoteException | RepositoryException e) {
999            throw new RemoteException(e);
1000        }
1001    }
1002
1003    @Override
1004    public Collection<Stats> getValueStats(String repository, String param, String tu) throws RemoteException {
1005        ArrayList<Stats> stats = new ArrayList<>();
1006
1007        try {
1008            Window w = TimeUnit.find(tu).window();
1009            ValueMetric m = MsValueMetric.find(param).metric();
1010
1011            JcrRepository repo = (JcrRepository) connector().find(repository).repository();
1012
1013            RepositoryStatistics repositoryStatistics = repo.getRepositoryStatistics();
1014            History history = repositoryStatistics.getHistory(m, w);
1015
1016            Statistics[] s = history.getStats();
1017            for (int i = 0; i < s.length; i++) {
1018                double min = s[i] != null ? s[i].getMinimum() : 0;
1019                double max = s[i] != null ? s[i].getMaximum() : 0;
1020                double avg = s[i] != null ? s[i].getMean() : 0;
1021                stats.add(new Stats(min, max, avg));
1022            }
1023        } catch (Exception e) {
1024            throw new RemoteException(e);
1025        }
1026        return stats;
1027    }
1028
1029    @Override
1030    @SuppressWarnings("unchecked")
1031    public Collection<Stats> getDurationStats(String repository, String param, String tu) throws RemoteException {
1032        ArrayList<Stats> stats = new ArrayList();
1033
1034        try {
1035            Window w = TimeUnit.find(tu).window();
1036            DurationMetric m = MsDurationMetric.find(param).metric();
1037
1038            JcrRepository repo = (JcrRepository) connector().find(repository).repository();
1039
1040            RepositoryStatistics repositoryStatistics = repo.getRepositoryStatistics();
1041            History history = repositoryStatistics.getHistory(m, w);
1042
1043            Statistics[] s = history.getStats();
1044            for (int i = 0; i < s.length; i++) {
1045                double min = s[i] != null ? s[i].getMinimum() : 0;
1046                double max = s[i] != null ? s[i].getMaximum() : 0;
1047                double avg = s[i] != null ? s[i].getMean() : 0;
1048                stats.add(new Stats(min, max, avg));
1049            }
1050        } catch (Exception e) {
1051            throw new RemoteException(e);
1052        }
1053        return stats;
1054    }
1055
1056    @Override
1057    public String[] getValueMetrics() throws RemoteException {
1058        return MsValueMetric.getNames();
1059    }
1060
1061    @Override
1062    public String[] getDurationMetrics() throws RemoteException {
1063        return MsDurationMetric.getNames();
1064    }
1065
1066    @Override
1067    public String[] getTimeUnits() throws RemoteException {
1068        return TimeUnit.names();
1069    }
1070
1071        
1072    private class SimplePrincipal implements Principal {
1073
1074        private String name;
1075
1076        protected SimplePrincipal( String name ) {
1077            this.name = name;
1078        }
1079
1080        @Override
1081        public String getName() {
1082            return name;
1083        }
1084    }
1085    
1086    private Connector loadConnector(String clsName, ServletContext context) throws RemoteException {
1087        try {
1088            Class<?> cls = getClass().getClassLoader().loadClass(clsName);
1089            Connector connector = (Connector) cls.newInstance();
1090            connector.start(context);
1091            return connector;
1092        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | RemoteException e) {
1093            throw new RemoteException(e);
1094        }
1095    }
1096    
1097    private boolean isUnknown(Object o) {
1098        return o == null;
1099    }
1100    
1101}