001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors.
008     *
009     * Unless otherwise indicated, all code in JBoss DNA is licensed
010     * to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     * 
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.connector.inmemory;
025    
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.LinkedList;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    import java.util.UUID;
033    import org.jboss.dna.common.i18n.I18n;
034    import org.jboss.dna.common.util.CheckArg;
035    import org.jboss.dna.graph.DnaLexicon;
036    import org.jboss.dna.graph.ExecutionContext;
037    import org.jboss.dna.graph.GraphI18n;
038    import org.jboss.dna.graph.JcrLexicon;
039    import org.jboss.dna.graph.Location;
040    import org.jboss.dna.graph.property.Name;
041    import org.jboss.dna.graph.property.Path;
042    import org.jboss.dna.graph.property.PathFactory;
043    import org.jboss.dna.graph.property.PathNotFoundException;
044    import org.jboss.dna.graph.property.Property;
045    import org.jboss.dna.graph.property.PropertyFactory;
046    import org.jboss.dna.graph.property.Path.Segment;
047    import org.jboss.dna.graph.request.CloneWorkspaceRequest;
048    import org.jboss.dna.graph.request.CopyBranchRequest;
049    import org.jboss.dna.graph.request.CreateNodeRequest;
050    import org.jboss.dna.graph.request.CreateWorkspaceRequest;
051    import org.jboss.dna.graph.request.DeleteBranchRequest;
052    import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
053    import org.jboss.dna.graph.request.GetWorkspacesRequest;
054    import org.jboss.dna.graph.request.InvalidWorkspaceException;
055    import org.jboss.dna.graph.request.MoveBranchRequest;
056    import org.jboss.dna.graph.request.ReadAllChildrenRequest;
057    import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
058    import org.jboss.dna.graph.request.Request;
059    import org.jboss.dna.graph.request.UpdatePropertiesRequest;
060    import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
061    import org.jboss.dna.graph.request.processor.RequestProcessor;
062    
063    /**
064     * A {@link RequestProcessor} implementation that operates on an {@link InMemoryRepository} and its
065     * {@link InMemoryRepository.Workspace workspaces}.
066     */
067    public class InMemoryRequestProcessor extends RequestProcessor {
068        private final PathFactory pathFactory;
069        private final PropertyFactory propertyFactory;
070        private final InMemoryRepository repository;
071    
072        protected InMemoryRequestProcessor( ExecutionContext context,
073                                            InMemoryRepository repository ) {
074            super(repository.getSourceName(), context);
075            this.repository = repository;
076            pathFactory = context.getValueFactories().getPathFactory();
077            propertyFactory = context.getPropertyFactory();
078        }
079    
080        @Override
081        public void process( ReadAllChildrenRequest request ) {
082            InMemoryRepository.Workspace workspace = getWorkspace(request, request.inWorkspace());
083            InMemoryNode node = getTargetNode(workspace, request, request.of());
084            if (node == null) return;
085            Location actualLocation = getActualLocation(request.of().getPath(), node);
086            Path path = actualLocation.getPath();
087            // Get the names of the children ...
088            List<InMemoryNode> children = node.getChildren();
089            for (InMemoryNode child : children) {
090                Segment childName = child.getName();
091                Path childPath = pathFactory.create(path, childName);
092                request.addChild(childPath, propertyFactory.create(DnaLexicon.UUID, child.getUuid()));
093            }
094            request.setActualLocationOfNode(actualLocation);
095            setCacheableInfo(request);
096        }
097    
098        @Override
099        public void process( ReadAllPropertiesRequest request ) {
100            InMemoryRepository.Workspace workspace = getWorkspace(request, request.inWorkspace());
101            InMemoryNode node = getTargetNode(workspace, request, request.at());
102            if (node == null) return;
103            // Get the properties of the node ...
104            Location actualLocation = getActualLocation(request.at().getPath(), node);
105            request.addProperty(propertyFactory.create(DnaLexicon.UUID, node.getUuid()));
106            for (Property property : node.getProperties().values()) {
107                request.addProperty(property);
108            }
109            request.setActualLocationOfNode(actualLocation);
110            setCacheableInfo(request);
111        }
112    
113        @Override
114        public void process( CopyBranchRequest request ) {
115            InMemoryRepository.Workspace workspace = getWorkspace(request, request.fromWorkspace());
116            InMemoryRepository.Workspace newWorkspace = getWorkspace(request, request.intoWorkspace());
117            if (workspace == null || newWorkspace == null) return;
118            InMemoryNode node = getTargetNode(workspace, request, request.from());
119            if (node == null) return;
120            // Look up the new parent, which must exist ...
121            Path newParentPath = request.into().getPath();
122            Name desiredName = request.desiredName();
123            InMemoryNode newParent = newWorkspace.getNode(newParentPath);
124            Map<UUID, UUID> copyMap = workspace.equals(newWorkspace) ? new HashMap<UUID, UUID>() : null;
125            InMemoryNode newNode = workspace.copyNode(getExecutionContext(),
126                                                      node,
127                                                      newWorkspace,
128                                                      newParent,
129                                                      desiredName,
130                                                      true,
131                                                      copyMap);
132            Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, newNode.getName());
133            Location oldLocation = getActualLocation(request.from().getPath(), node);
134            Location newLocation = Location.create(newPath, newNode.getUuid());
135            request.setActualLocations(oldLocation, newLocation);
136        }
137    
138        @Override
139        public void process( CreateNodeRequest request ) {
140            InMemoryRepository.Workspace workspace = getWorkspace(request, request.inWorkspace());
141            if (workspace == null) return;
142            Path parent = request.under().getPath();
143            CheckArg.isNotNull(parent, "request.under().getPath()");
144            InMemoryNode node = null;
145            // Look up the parent node, which must exist ...
146            InMemoryNode parentNode = workspace.getNode(parent);
147            if (parentNode == null) {
148                Path lowestExisting = workspace.getLowestExistingPath(parent);
149                request.setError(new PathNotFoundException(request.under(), lowestExisting,
150                                                           GraphI18n.inMemoryNodeDoesNotExist.text(parent)));
151            }
152            UUID uuid = null;
153            for (Property property : request.properties()) {
154                if (property.getName().equals(DnaLexicon.UUID) || property.getName().equals(JcrLexicon.UUID)) {
155                    uuid = getExecutionContext().getValueFactories().getUuidFactory().create(property.getValues().next());
156                    break;
157                }
158            }
159            node = workspace.createNode(getExecutionContext(), parentNode, request.named(), uuid);
160            assert node != null;
161            Path path = getExecutionContext().getValueFactories().getPathFactory().create(parent, node.getName());
162            // Now add the properties to the supplied node ...
163            for (Property property : request.properties()) {
164                Name propName = property.getName();
165                if (property.size() == 0) {
166                    node.getProperties().remove(propName);
167                    continue;
168                }
169                if (!propName.equals(DnaLexicon.UUID)) {
170                    node.getProperties().put(propName, property);
171                }
172            }
173            Location actualLocation = getActualLocation(path, node);
174            request.setActualLocationOfNode(actualLocation);
175        }
176    
177        @Override
178        public void process( DeleteBranchRequest request ) {
179            InMemoryRepository.Workspace workspace = getWorkspace(request, request.inWorkspace());
180            if (workspace == null) return;
181            InMemoryNode node = getTargetNode(workspace, request, request.at());
182            if (node == null) return;
183            workspace.removeNode(getExecutionContext(), node);
184            Location actualLocation = getActualLocation(request.at().getPath(), node);
185            request.setActualLocationOfNode(actualLocation);
186        }
187    
188        @Override
189        public void process( MoveBranchRequest request ) {
190            InMemoryRepository.Workspace workspace = getWorkspace(request, request.inWorkspace());
191            if (workspace == null) return;
192    
193            InMemoryNode node = getTargetNode(workspace, request, request.from());
194            if (node == null) return;
195            // Look up the new parent, which must exist ...
196            Path newParentPath = request.into().getPath();
197            InMemoryNode newParent = workspace.getNode(newParentPath);
198            workspace.moveNode(getExecutionContext(), node, request.desiredName(), workspace, newParent);
199            assert node.getParent() == newParent;
200            Path newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, node.getName());
201            Location oldLocation = getActualLocation(request.from().getPath(), node);
202            Location newLocation = Location.create(newPath, newParent.getUuid());
203            request.setActualLocations(oldLocation, newLocation);
204        }
205    
206        @Override
207        public void process( UpdatePropertiesRequest request ) {
208            InMemoryRepository.Workspace workspace = getWorkspace(request, request.inWorkspace());
209            InMemoryNode node = getTargetNode(workspace, request, request.on());
210            if (node == null) return;
211            // Now set (or remove) the properties to the supplied node ...
212            for (Map.Entry<Name, Property> propertyEntry : request.properties().entrySet()) {
213                Property property = propertyEntry.getValue();
214                if (property == null) {
215                    node.getProperties().remove(propertyEntry.getKey());
216                    continue;
217                }
218                Name propName = property.getName();
219                if (!propName.equals(DnaLexicon.UUID)) {
220                    node.getProperties().put(propName, property);
221                }
222            }
223            Location actualLocation = getActualLocation(request.on().getPath(), node);
224            request.setActualLocationOfNode(actualLocation);
225        }
226    
227        /**
228         * {@inheritDoc}
229         * 
230         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest)
231         */
232        @Override
233        public void process( CreateWorkspaceRequest request ) {
234            InMemoryRepository.Workspace workspace = repository.createWorkspace(getExecutionContext(),
235                                                                                request.desiredNameOfNewWorkspace(),
236                                                                                request.conflictBehavior());
237            if (workspace == null) {
238                String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(request.desiredNameOfNewWorkspace(),
239                                                                               repository.getSourceName());
240                request.setError(new InvalidWorkspaceException(msg));
241            } else {
242                InMemoryNode root = workspace.getRoot();
243                request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid()));
244                request.setActualWorkspaceName(workspace.getName());
245            }
246        }
247    
248        /**
249         * {@inheritDoc}
250         * 
251         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest)
252         */
253        @Override
254        public void process( DestroyWorkspaceRequest request ) {
255            if (!repository.destroyWorkspace(request.workspaceName())) {
256                String msg = GraphI18n.workspaceDoesNotExistInRepository.text(request.workspaceName(), repository.getSourceName());
257                request.setError(new InvalidWorkspaceException(msg));
258            }
259        }
260    
261        /**
262         * {@inheritDoc}
263         * 
264         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest)
265         */
266        @Override
267        public void process( GetWorkspacesRequest request ) {
268            Set<String> names = repository.getWorkspaceNames();
269            request.setAvailableWorkspaceNames(new HashSet<String>(names));
270            setCacheableInfo(request);
271        }
272    
273        /**
274         * {@inheritDoc}
275         * 
276         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest)
277         */
278        @Override
279        public void process( VerifyWorkspaceRequest request ) {
280            InMemoryRepository.Workspace original = getWorkspace(request, request.workspaceName());
281            if (original != null) {
282                Path path = getExecutionContext().getValueFactories().getPathFactory().createRootPath();
283                request.setActualRootLocation(Location.create(path, original.getRoot().getUuid()));
284                request.setActualWorkspaceName(original.getName());
285            }
286        }
287    
288        /**
289         * {@inheritDoc}
290         * 
291         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
292         */
293        @Override
294        public void process( CloneWorkspaceRequest request ) {
295            // Find the original workspace that we're cloning ...
296            final ExecutionContext context = getExecutionContext();
297            String targetWorkspaceName = request.desiredNameOfTargetWorkspace();
298            String nameOfWorkspaceToBeCloned = request.nameOfWorkspaceToBeCloned();
299            InMemoryRepository.Workspace original = repository.getWorkspace(context, nameOfWorkspaceToBeCloned);
300            InMemoryRepository.Workspace target = repository.getWorkspace(context, targetWorkspaceName);
301            if (original == null) {
302                switch (request.cloneConflictBehavior()) {
303                    case DO_NOT_CLONE:
304                        String msg = GraphI18n.workspaceDoesNotExistInRepository.text(nameOfWorkspaceToBeCloned,
305                                                                                      repository.getSourceName());
306                        request.setError(new InvalidWorkspaceException(msg));
307                        return;
308                    case SKIP_CLONE:
309                        target = repository.createWorkspace(context, targetWorkspaceName, request.targetConflictBehavior());
310                        if (target == null) {
311                            msg = GraphI18n.workspaceAlreadyExistsInRepository.text(targetWorkspaceName, repository.getSourceName());
312                            request.setError(new InvalidWorkspaceException(msg));
313                        } else {
314                            InMemoryNode root = target.getRoot();
315                            request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid()));
316                            request.setActualWorkspaceName(target.getName());
317                        }
318                        return;
319                }
320            }
321            assert original != null;
322            target = repository.createWorkspace(context,
323                                                targetWorkspaceName,
324                                                request.targetConflictBehavior(),
325                                                nameOfWorkspaceToBeCloned);
326            if (target == null) {
327                // Since the original was there, the only reason the target wasn't created was because the workspace already existed
328                // ...
329                String msg = GraphI18n.workspaceAlreadyExistsInRepository.text(targetWorkspaceName, repository.getSourceName());
330                request.setError(new InvalidWorkspaceException(msg));
331            } else {
332                InMemoryNode root = target.getRoot();
333                request.setActualRootLocation(Location.create(pathFactory.createRootPath(), root.getUuid()));
334                request.setActualWorkspaceName(target.getName());
335            }
336        }
337    
338        protected Location getActualLocation( Path path,
339                                              InMemoryNode node ) {
340            if (path == null) {
341                // Find the path on the node ...
342                LinkedList<Path.Segment> segments = new LinkedList<Path.Segment>();
343                InMemoryNode n = node;
344                while (n != null) {
345                    if (n.getParent() == null) break;
346                    segments.addFirst(n.getName());
347                    n = n.getParent();
348                }
349                path = pathFactory.createAbsolutePath(segments);
350            }
351            return Location.create(path, node.getUuid());
352        }
353    
354        protected InMemoryRepository.Workspace getWorkspace( Request request,
355                                                             String workspaceName ) {
356            // Get the workspace for this request ...
357            InMemoryRepository.Workspace workspace = repository.getWorkspace(getExecutionContext(), workspaceName);
358            if (workspace == null) {
359                String msg = GraphI18n.workspaceDoesNotExistInRepository.text(workspaceName, repository.getSourceName());
360                request.setError(new InvalidWorkspaceException(msg));
361            }
362            return workspace;
363        }
364    
365        protected InMemoryNode getTargetNode( InMemoryRepository.Workspace workspace,
366                                              Request request,
367                                              Location location ) {
368            if (workspace == null) return null;
369            // Check first for the UUID ...
370            InMemoryNode node = null;
371            UUID uuid = location.getUuid();
372            if (uuid != null) {
373                node = workspace.getNode(uuid);
374            }
375            Path path = null;
376            if (node == null) {
377                // Look up the node with the supplied path ...
378                path = location.getPath();
379                if (path != null) {
380                    node = workspace.getNode(path);
381                }
382            }
383            if (node == null) {
384                if (path == null) {
385                    if (uuid == null) {
386                        // Missing both path and UUID ...
387                        I18n msg = GraphI18n.inMemoryConnectorRequestsMustHavePathOrUuid;
388                        request.setError(new IllegalArgumentException(msg.text()));
389                        return null;
390                    }
391                    // Missing path, and could not find by UUID ...
392                    request.setError(new PathNotFoundException(location, pathFactory.createRootPath(),
393                                                               GraphI18n.inMemoryNodeDoesNotExist.text(path)));
394                    return null;
395                }
396                // Could not find the node given the supplied path, so find the lowest path that does exist ...
397                Path lowestExisting = workspace.getLowestExistingPath(path);
398                request.setError(new PathNotFoundException(location, lowestExisting, GraphI18n.inMemoryNodeDoesNotExist.text(path)));
399            }
400            return node;
401        }
402    }