text:
/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.modeshape.connector.filesystem;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import org.modeshape.common.util.FileUtil;
import org.modeshape.common.util.IoUtil;
import org.modeshape.common.util.NamedThreadFactory;
import org.modeshape.common.util.SecureHash;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.JcrI18n;
import org.modeshape.jcr.JcrLexicon;
import org.modeshape.jcr.JcrNtLexicon;
import org.modeshape.jcr.RepositoryConfiguration;
import org.modeshape.jcr.api.Binary;
import org.modeshape.jcr.api.nodetype.NodeTypeManager;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.cache.DocumentStoreException;
import org.modeshape.jcr.federation.NoExtraPropertiesStorage;
import org.modeshape.jcr.spi.federation.Connector;
import org.modeshape.jcr.spi.federation.ConnectorChangeSet;
import org.modeshape.jcr.spi.federation.ConnectorException;
import org.modeshape.jcr.spi.federation.DocumentChanges;
import org.modeshape.jcr.spi.federation.DocumentReader;
import org.modeshape.jcr.spi.federation.DocumentWriter;
import org.modeshape.jcr.spi.federation.PageKey;
import org.modeshape.jcr.spi.federation.Pageable;
import org.modeshape.jcr.spi.federation.WritableConnector;
import org.modeshape.jcr.value.BinaryValue;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.Property;
import org.modeshape.jcr.value.binary.ExternalBinaryValue;
import org.modeshape.jcr.value.binary.UrlBinaryValue;
import org.modeshape.schematic.document.Document;

/**
 * {@link Connector} implementation that exposes a single directory on the local file system. This connector has several
 * properties that must be configured via the {@link RepositoryConfiguration}:
 *

 *
directoryPath - The path to the file or folder that is to be accessed by this connector.

 *
readOnly - A boolean flag that specifies whether this source can create/modify/remove files
 * and directories on the file system to reflect changes in the JCR content. By default, sources are not read-only.

 *
addMimeTypeMixin - A boolean flag that specifies whether this connector should add the
 * 'mix:mimeType' mixin to the 'nt:resource' nodes to include the 'jcr:mimeType' property. If set to true, the MIME
 * type is computed immediately when the 'nt:resource' node is accessed, which might be expensive for larger files. This is
 * false by default.

 *
extraPropertyStorage - An optional string flag that specifies how this source handles "extra"
 * properties that are not stored via file system attributes. See {@link #extraPropertiesStorage} for details. By default, extra
 * properties are stored in the same DB that the repository uses.

 *
exclusionPattern - Optional property that specifies a regular expression that is used to
 * determine which files and folders in the underlying file system are not exposed through this connector. Files and folders with
 * a name that matches the provided regular expression will not be exposed by this source.

 *
inclusionPattern - Optional property that specifies a regular expression that is used to
 * determine which files and folders in the underlying file system are exposed through this connector. Files and folders with a
 * name that matches the provided regular expression will be exposed by this source.

 *

 * Inclusion and exclusion patterns can be used separately or in combination. For example, consider these cases:
 *

 *

 * Inclusion Pattern
 * Exclusion Pattern
 * Examples
 *

 *

 * (.+)\\.txt$
 *
 * Includes only files and directories whose names end in ".txt" (e.g., "something.txt" ), but does
 * not include files and other folders such as "something.jar" or "something.txt.zip".
 *

 *

 * (.+)\\.txt$
 * my.txt
 * Includes only files and directories whose names end in ".txt" (e.g., "something.txt" ) with the
 * exception of "my.txt", and does not include files and other folders such as "something.jar" or "
 * something.txt.zip".
 *

 *

 * my.txt
 * .+
 * Excludes all files and directories except any named "my.txt".
 *

 *

 */
public class FileSystemConnector extends WritableConnector implements Pageable {
    protected static final int DEFAULT_PAGE_SIZE = 20;

    private static final String FILE_SEPARATOR = System.getProperty("file.separator");
    private static final String DELIMITER = "/";
    private static final String NT_FOLDER = "nt:folder";
    private static final String NT_FILE = "nt:file";
    private static final String NT_RESOURCE = "nt:resource";
    private static final String MIX_MIME_TYPE = "mix:mimeType";
    private static final String JCR_PRIMARY_TYPE = "jcr:primaryType";
    private static final String JCR_DATA = "jcr:data";
    private static final String JCR_MIME_TYPE = "jcr:mimeType";
    private static final String JCR_CREATED = "jcr:created";
    private static final String JCR_CREATED_BY = "jcr:createdBy";
    private static final String JCR_LAST_MODIFIED = "jcr:lastModified";
    private static final String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
    private static final String JCR_CONTENT = "jcr:content";
    private static final String JCR_CONTENT_SUFFIX = DELIMITER + JCR_CONTENT;
    private static final int JCR_CONTENT_SUFFIX_LENGTH = JCR_CONTENT_SUFFIX.length();

    private static final String EXTRA_PROPERTIES_JSON = "json";
    private static final String EXTRA_PROPERTIES_LEGACY = "legacy";
    private static final String EXTRA_PROPERTIES_NONE = "none";

    /**
     * The string path for a {@link File} object that represents the top-level directory accessed by this connector. This is set
     * via reflection and is required for this connector.
     */
    private String directoryPath;
    private File directory;

    /**
     * A string that is created in the {@link #initialize(NamespaceRegistry, NodeTypeManager)} method that represents the absolute
     * path to the {@link #directory}. This path is removed from an absolute path of a file to obtain the ID of the node.
     */
    private String directoryAbsolutePath;
    private int directoryAbsolutePathLength;

    /**
     * A boolean flag that specifies whether this connector should add the 'mix:mimeType' mixin to the 'nt:resource' nodes to
     * include the 'jcr:mimeType' property. If set to true, the MIME type is computed immediately when the
     * 'nt:resource' node is accessed, which might be expensive for larger files. This is false by default.
     */
    private boolean addMimeTypeMixin = false;

    /**
     * The regular expression that, if matched by a file or folder, indicates that the file or folder should be included.
     */
    private String inclusionPattern;

    /**
     * The regular expression that, if matched by a file or folder, indicates that the file or folder should be ignored.
     */
    private String exclusionPattern;

    /**
     * The maximum number of children a folder will expose at any given time.
     */
    private int pageSize = DEFAULT_PAGE_SIZE;

    /**
     * The {@link FilenameFilter} implementation that is instantiated in the
     * {@link #initialize(NamespaceRegistry, NodeTypeManager)} method.
     */
    private InclusionExclusionFilenameFilter filenameFilter;

    /**
     * Flag which enables or disables external monitoring, meaning firing repository events when changes to the file system occur.
     */
    private boolean enableEvents = false;

    /**
     * The thread which performs external monitoring
     */
    private Future<Void> monitoringTask;

    /**
     * A string that specifies how the "extra" properties are to be stored, where an "extra" property is any JCR property that
     * cannot be stored natively on the file system as file attributes. This field is set via reflection, and the value is
     * expected to be one of these valid values:
     *

     *
"store" - Any extra properties are stored in the same DB where the content is stored. This
     * is the default and is used if the actual value doesn't match any of the other accepted values.

     *
"json" - Any extra properties are stored in a JSON file next to the file or directory.

     *
"legacy" - Any extra properties are stored in a ModeShape 2.x-compatible file next to the file or
     * directory. This is generally discouraged unless you were using ModeShape 2.x and have a directory structure that already
     * contains these files.

     *
"none" - An extra properties that prevents the storage of extra properties by throwing an exception when
     * such extra properties are defined.

     *

     */
    private String extraPropertiesStorage;

    /**
     * A boolean which determines whether for external binary values (i.e. {@link UrlBinaryValue}) the SHA1 is computed based on
     * the content of the file itself or whether it's computed based on the URL string. This is {@code true} by default, but if
     * the connector needs to deal with very large values it might be worth turning off.
     */
    private boolean contentBasedSha1 = true;

    private NamespaceRegistry registry;

    @Override
    public void initialize( NamespaceRegistry registry,
                            NodeTypeManager nodeTypeManager ) throws RepositoryException, IOException {
        super.initialize(registry, nodeTypeManager);
        this.registry = registry;

        // Initialize the directory path field that has been set via reflection when this method is called...
        checkFieldNotNull(directoryPath, "directoryPath");
        directory = new File(directoryPath);
        if (!directory.exists() || !directory.isDirectory()) {
            String msg = JcrI18n.fileConnectorTopLevelDirectoryMissingOrCannotBeRead.text(getSourceName(), "directoryPath");
            throw new RepositoryException(msg);
        }
        if (!directory.canRead() && !directory.setReadable(true)) {
            String msg = JcrI18n.fileConnectorTopLevelDirectoryMissingOrCannotBeRead.text(getSourceName(), "directoryPath");
            throw new RepositoryException(msg);
        }
        directoryAbsolutePath = directory.getAbsolutePath();
        if (!directoryAbsolutePath.endsWith(FILE_SEPARATOR)) directoryAbsolutePath = directoryAbsolutePath + FILE_SEPARATOR;
        directoryAbsolutePathLength = directoryAbsolutePath.length() - FILE_SEPARATOR.length(); // does NOT include the separator

        // Initialize the filename filter ...
        filenameFilter = new InclusionExclusionFilenameFilter();
        if (exclusionPattern != null) filenameFilter.setExclusionPattern(exclusionPattern);
        if (inclusionPattern != null) filenameFilter.setInclusionPattern(inclusionPattern);

        // Set up the extra properties storage ...
        if (EXTRA_PROPERTIES_JSON.equalsIgnoreCase(extraPropertiesStorage)) {
            JsonSidecarExtraPropertyStore store = new JsonSidecarExtraPropertyStore(this, translator());
            setExtraPropertiesStore(store);
            filenameFilter.setExtraPropertiesExclusionPattern(store.getExclusionPattern());
        } else if (EXTRA_PROPERTIES_LEGACY.equalsIgnoreCase(extraPropertiesStorage)) {
            LegacySidecarExtraPropertyStore store = new LegacySidecarExtraPropertyStore(this);
            setExtraPropertiesStore(store);
            filenameFilter.setExtraPropertiesExclusionPattern(store.getExclusionPattern());
        } else if (EXTRA_PROPERTIES_NONE.equalsIgnoreCase(extraPropertiesStorage)) {
            setExtraPropertiesStore(new NoExtraPropertiesStorage(this));
        }
        // otherwise use the default extra properties storage

        if (enableEvents) {
            try {
                WatchService watchService = FileSystems.getDefault().newWatchService();
                monitoringTask = Executors.newSingleThreadExecutor(new NamedThreadFactory("modeshape-fs-connector-monitor"))
                                          .submit(new MonitoringTask(watchService, this, Paths.get(directoryAbsolutePath)));
            } catch (UnsupportedOperationException e) {
                log().warn("Unable to to turn on monitoring, because it is not supported on this OS");
            }
        }
    }

    @Override
    public void shutdown() {
        if (enableEvents && monitoringTask != null) {
            try {
                monitoringTask.cancel(true);
            } finally {
                monitoringTask = null;
            }
        }
    }

    /**
     * Get the namespace registry.
     *
     * @return the namespace registry; never null
     */
    NamespaceRegistry registry() {
        return registry;
    }

    /**
     * Utility method for determining if the supplied identifier is for the "jcr:content" child node of a file. * Subclasses may
     * override this method to change the format of the identifiers, but in that case should also override the
     * {@link #fileFor(String)}, {@link #isRoot(String)}, and {@link #idFor(File)} methods.
     *
     * @param id the identifier; may not be null
     * @return true if the identifier signals the "jcr:content" child node of a file, or false otherwise
     * @see #isRoot(String)
     * @see #fileFor(String)
     * @see #idFor(File)
     */
    protected boolean isContentNode( String id ) {
        return id.endsWith(JCR_CONTENT_SUFFIX);
    }

    /**
     * Utility method for obtaining the {@link File} object that corresponds to the supplied identifier. Subclasses may override
     * this method to change the format of the identifiers, but in that case should also override the {@link #isRoot(String)},
     * {@link #isContentNode(String)}, and {@link #idFor(File)} methods.
     *
     * @param id the identifier; may not be null
     * @return the File object for the given identifier
     * @see #isRoot(String)
     * @see #isContentNode(String)
     * @see #idFor(File)
     */
    protected File fileFor( String id ) {
        assert id.startsWith(DELIMITER);
        if (id.endsWith(DELIMITER)) {
            id = id.substring(0, id.length() - DELIMITER.length());
        }
        if (isContentNode(id)) {
            id = id.substring(0, id.length() - JCR_CONTENT_SUFFIX_LENGTH);
        }
        return new File(directory, id);
    }

    /**
     * Utility method for determining if the node identifier is the identifier of the root node in this external source.
     * Subclasses may override this method to change the format of the identifiers, but in that case should also override the
     * {@link #fileFor(String)}, {@link #isContentNode(String)}, and {@link #idFor(File)} methods.
     *
     * @param id the identifier; may not be null
     * @return true if the identifier is for the root of this source, or false otherwise
     * @see #isContentNode(String)
     * @see #fileFor(String)
     * @see #idFor(File)
     */
    protected boolean isRoot( String id ) {
        return DELIMITER.equals(id);
    }

    /**
     * Utility method for determining the node identifier for the supplied file. Subclasses may override this method to change the
     * format of the identifiers, but in that case should also override the {@link #fileFor(String)},
     * {@link #isContentNode(String)}, and {@link #isRoot(String)} methods.
     *
     * @param file the file; may not be null
     * @return the node identifier; never null
     * @see #isRoot(String)
     * @see #isContentNode(String)
     * @see #fileFor(String)
     */
    protected String idFor( File file ) {
        String path = file.getAbsolutePath();
        if (!path.startsWith(directoryAbsolutePath)) {
            if (directory.getAbsolutePath().equals(path)) {
                // This is the root
                return DELIMITER;
            }
            String msg = JcrI18n.fileConnectorNodeIdentifierIsNotWithinScopeOfConnector.text(getSourceName(), directoryPath, path);
            throw new DocumentStoreException(path, msg);
        }
        String id = path.substring(directoryAbsolutePathLength);
        id = id.replaceAll(Pattern.quote(FILE_SEPARATOR), DELIMITER);
        assert id.startsWith(DELIMITER);
        return id;
    }

    /**
     * Utility method for creating a {@link BinaryValue} for the given {@link File} object. Subclasses should rarely override this
     * method.
     *
     * @param file the file; may not be null
     * @return the BinaryValue; never null
     */
    protected ExternalBinaryValue binaryFor( File file ) {
        try {
            return createBinaryValue(file);
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Utility method to create a {@link BinaryValue} object for the given file. Subclasses should rarely override this method,
     * since the {@link UrlBinaryValue} will be applicable in most situations.
     *
     * @param file the file for which the {@link BinaryValue} is to be created; never null
     * @return the binary value; never null
     * @throws IOException if there is an error creating the value
     */
    protected ExternalBinaryValue createBinaryValue( File file ) throws IOException {
        URL content = createUrlForFile(file);
        return new UrlBinaryValue(sha1(file), getSourceName(), content, file.length(), file.getName(), getMimeTypeDetector());
    }

    /**
     * Computes the SHA1 for the given file. By default, this method will look at the
     * {@link FileSystemConnector#contentBasedSha1()} flag and either take the URL of the file (using @see
     * java.util.File#toURI().toURL() and return the SHA1 of the URL string or return the SHA1 of the entire file content.
     *
     * @param file a {@link File} instance; never null
     * @return the SHA1 of the file.
     */
    protected String sha1( File file ) {
        try {
            if (contentBasedSha1()) {
                byte[] hash = SecureHash.getHash(SecureHash.Algorithm.SHA_1, file);
                return StringUtil.getHexString(hash);
            }
            return SecureHash.sha1(createUrlForFile(file).toString());
        } catch (Exception e) {
            throw new ConnectorException(e);
        }
    }

    /**
     * Construct a {@link URL} object for the given file, to be used within the {@link Binary} value representing the "jcr:data"
     * property of a 'nt:resource' node.
     *

     * Subclasses can override this method to transform the URL into something different. For example, if the files are being
     * served by a web server, the overridden method might transform the file-based URL into the corresponding HTTP-based URL.
     *

     *
     * @param file the file for which the URL is to be created; never null
     * @return the URL for the file; never null
     * @throws IOException if there is an error creating the URL
     */
    protected URL createUrlForFile( File file ) throws IOException {
        return file.toURI().toURL();
    }

    protected File createFileForUrl( URL url ) throws URISyntaxException {
        return new File(url.toURI());
    }

    protected boolean contentBasedSha1() {
        return contentBasedSha1;
    }

    /**
     * Utility method to determine if the file is excluded by the inclusion/exclusion filter.
     *
     * @param file the file
     * @return true if the file is excluded, or false if it is to be included
     */
    protected boolean isExcluded( File file ) {
        return !filenameFilter.accept(file.getParentFile(), file.getName());
    }

    /**
     * Utility method to ensure that the file is writable by this connector.
     *
     * @param id the identifier of the node
     * @param file the file
     * @throws DocumentStoreException if the file is expected to be writable but is not or is excluded, or if the connector is
     *         readonly
     */
    protected void checkFileNotExcluded( String id,
                                         File file ) {
        if (isExcluded(file)) {
            String msg = JcrI18n.fileConnectorCannotStoreFileThatIsExcluded.text(getSourceName(), id, file.getAbsolutePath());
            throw new DocumentStoreException(id, msg);
        }
    }

    @Override
    public boolean hasDocument( String id ) {
        return fileFor(id).exists();
    }

    /**
     * Hook for processing other node types (not resources or regular files)
     * @param id
     * @param file
     * @param isRoot
     * @param isResource
     * @return DocumentWriter if node is recognized and processed; otherwise
     * null (signaling subsequent fallback to default processing)
     */
    protected DocumentWriter writerFor(String id, File file, boolean isRoot, boolean isResource) {
        return null;
    }

    @Override
    public Document getDocumentById( String id ) {
        File file = fileFor(id);
        boolean isRoot = isRoot(id);
        // make sure the root folder is never excluded....
        if (!isRoot && (isExcluded(file) || !acceptFile(file))) return null;
        boolean isResource = isContentNode(id);
        File parentFile = file.getParentFile();
        DocumentWriter writer = writerFor(id, file, isRoot, isResource);
        if (writer != null) {
            // node configuration initialized by writerFor method
        } else if (isResource) {
            writer = newDocument(id);
            BinaryValue binaryValue = binaryFor(file);
            writer.setPrimaryType(NT_RESOURCE);
            writer.addProperty(JCR_DATA, binaryValue);
            if (addMimeTypeMixin) {
                String mimeType = null;
                try {
                    mimeType = binaryValue.getMimeType();
                } catch (Throwable e) {
                    getLogger().error(e, JcrI18n.couldNotGetMimeType, getSourceName(), id, e.getMessage());
                }
                writer.addProperty(JCR_MIME_TYPE, mimeType);
            }
            writer.addProperty(JCR_LAST_MODIFIED, lastModifiedTimeFor(file));
            writer.addProperty(JCR_LAST_MODIFIED_BY, ownerFor(file));

            // // make these binary not queryable. If we really want to query them, we need to switch to external binaries
            // writer.setNotQueryable();
            if (!isQueryable()) writer.setNotQueryable();
            parentFile = file;
        } else if (file.isFile()) {
            writer = newDocument(id);
            writer.setPrimaryType(NT_FILE);

            writer.addProperty(JCR_CREATED, createdTimeFor(file));
            writer.addProperty(JCR_CREATED_BY, ownerFor(file));
            String childId = contentChildId(id, isRoot);
            writer.addChild(childId, JCR_CONTENT);
            if (!isQueryable()) writer.setNotQueryable();
        } else {
            writer = newFolderWriter(id, file, 0);
        }

        if (!isRoot) {
            // Set the reference to the parent ...
            String parentId = idFor(parentFile);
            writer.setParents(parentId);
        }

        // Add the extra properties (if there are any), overwriting any properties with the same names
        // (e.g., jcr:primaryType, jcr:mixinTypes, jcr:mimeType, etc.) ...
        writer.addProperties(extraPropertiesStore().getProperties(id));

        // Add the 'mix:mixinType' mixin; if other mixins are stored in the extra properties, this will append ...
        if (addMimeTypeMixin) {
            writer.addMixinType(MIX_MIME_TYPE);
        }

        // Return the document ...
        return writer.document();
    }

    private DateTime createdTimeFor( File file ) {
        BasicFileAttributes basicFileAttributes = basicAttributesFor(file);
        return basicFileAttributes != null ? factories().getDateFactory().create(basicFileAttributes.creationTime().toMillis()) : factories().getDateFactory()
                                                                                .create(file.lastModified());
    }

    private DateTime lastModifiedTimeFor( File file ) {
        BasicFileAttributes basicFileAttributes = basicAttributesFor(file);
        return basicFileAttributes != null ? factories().getDateFactory().create(basicFileAttributes.lastModifiedTime()
                                                                                .toMillis()) : factories().getDateFactory()
                                                                                .create(file.lastModified());
    }

    private String ownerFor( File file ) {
        Path filePath = Paths.get(file.toURI());
        try {
            return Files.getOwner(filePath).getName();
        } catch (IOException e) {
            log().debug(e, "Unable to read the owner of '{0}'", filePath);
            return null;
        }
    }

    private BasicFileAttributes basicAttributesFor( File file ) {
        Path filePath = Paths.get(file.toURI());
        try {
            return Files.readAttributes(filePath, BasicFileAttributes.class);
        } catch (IOException e) {
            log().debug(e, "Unable to read attributes for '{0}'", filePath);
            return null;
        }
    }

    protected String contentChildId( String fileId,
                                     boolean isRoot ) {
        return isRoot ? JCR_CONTENT_SUFFIX : fileId + JCR_CONTENT_SUFFIX;
    }

    protected boolean acceptFile(File file) {
        // By default, only include as a child if we can access and read the file. Permissions might prevent us from
        // reading the file, and the file might not exist if it is a broken symlink (see MODE-1768 for details).
        return file.exists() && file.canRead() && (file.isFile() || file.isDirectory());
    }

    private DocumentWriter newFolderWriter( String id,
                                            File file,
                                            int offset ) {
        boolean root = isRoot(id);
        DocumentWriter writer = newDocument(id);
        writer.setPrimaryType(NT_FOLDER);
        writer.addProperty(JCR_CREATED, createdTimeFor(file));
        writer.addProperty(JCR_CREATED_BY, ownerFor(file));
        if (!isQueryable()) writer.setNotQueryable();
        File[] children = file.listFiles(filenameFilter);
        long totalChildren = 0;
        int nextOffset = 0;
        for (int i = 0; i < children.length; i++) {
            File child = children[i];
            if (acceptFile(child)) {
                // we need to count the total accessible children
                totalChildren++;
                // only add a child if it's in the current page
                if (i >= offset && i < offset + pageSize) {
                    // We use identifiers that contain the file/directory name ...
                    String childName = child.getName();
                    String childId = root ? DELIMITER + childName : id + DELIMITER + childName;
                    writer.addChild(childId, childName);
                    nextOffset = i + 1;
                }
            }
        }
        // if there are still accessible children add the next page
        if (nextOffset < totalChildren) {
            writer.addPage(id, nextOffset, pageSize, totalChildren);
        }
        return writer;
    }

    @Override
    public String getDocumentId( String path ) {
        String id = path; // this connector treats the ID as the path
        File file = fileFor(id);
        return file.exists() ? id : null;
    }

    @Override
    public Collection<String> getDocumentPathsById( String id ) {
        // this connector treats the ID as the path
        return Collections.singletonList(id);
    }

    @Override
    public ExternalBinaryValue getBinaryValue( String id ) {
        try {
            File f = createFileForUrl(new URL(id));
            return binaryFor(f);
        } catch (IOException | URISyntaxException e) {
            throw new DocumentStoreException(id, e);
        }
    }

    @Override
    public boolean removeDocument( String id ) {
        File file = fileFor(id);
        checkFileNotExcluded(id, file);
        // Remove the extra properties at the old location ...
        extraPropertiesStore().removeProperties(id);
        // Now remove the file (if it is there) ...
        if (!file.exists()) return false;
        FileUtil.delete(file); // recursive delete
        return true;
    }

    @Override
    public void storeDocument( Document document ) {
        // Create a new directory or file described by the document ...
        DocumentReader reader = readDocument(document);
        String id = reader.getDocumentId();
        File file = fileFor(id);
        checkFileNotExcluded(id, file);
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        if (!parent.canWrite()) {
            String parentPath = parent.getAbsolutePath();
            String msg = JcrI18n.fileConnectorCannotWriteToDirectory.text(getSourceName(), getClass(), parentPath);
            throw new DocumentStoreException(id, msg);
        }
        String primaryType = reader.getPrimaryTypeName();
        Map<Name, Property> properties = reader.getProperties();
        ExtraProperties extraProperties = extraPropertiesFor(id, false);
        extraProperties.addAll(properties).except(JCR_PRIMARY_TYPE, JCR_CREATED, JCR_LAST_MODIFIED, JCR_DATA);
        try {
            if (NT_FILE.equals(primaryType)) {
                file.createNewFile();
            } else if (NT_FOLDER.equals(primaryType)) {
                file.mkdirs();
            } else if (isContentNode(id)) {
                Property content = properties.get(JcrLexicon.DATA);
                BinaryValue binary = factories().getBinaryFactory().create(content.getFirstValue());
                OutputStream ostream = new BufferedOutputStream(new FileOutputStream(file));
                IoUtil.write(binary.getStream(), ostream);
                if (!NT_RESOURCE.equals(primaryType)) {
                    // This is the "jcr:content" child, but the primary type is non-standard so record it as an extra property
                    extraProperties.add(properties.get(JcrLexicon.PRIMARY_TYPE));
                }
            }
            extraProperties.save();
        } catch (RepositoryException | IOException e) {
            throw new DocumentStoreException(id, e);
        }
    }

    @Override
    public String newDocumentId( String parentId,
                                 Name newDocumentName,
                                 Name newDocumentPrimaryType ) {
        StringBuilder id = new StringBuilder(parentId);
        if (!parentId.endsWith(DELIMITER)) {
            id.append(DELIMITER);
        }

        // We're only using the name to check, which can be a bit dangerous if users don't follow the JCR conventions.
        // However, it matches what "isContentNode(...)" does.
        String childNameStr = getContext().getValueFactories().getStringFactory().create(newDocumentName);
        if (JCR_CONTENT.equals(childNameStr)) {
            // This is for the "jcr:content" node underneath a file node. Since this doesn't actually result in a file or folder
            // on the file system (it's merged into the file for the parent 'nt:file' node), we'll keep the "jcr" namespace
            // prefix in the ID so that 'isContentNode(...)' works properly ...
            id.append(childNameStr);
        } else {
            // File systems don't universally deal well with ':' in the names, and when they do it can be a bit awkward. Since we
            // don't often expect the node NAMES to contain namespaces (at leat with this connector), we'll just
            // use the local part for the ID ...
            id.append(newDocumentName.getLocalName());
            if (!StringUtil.isBlank(newDocumentName.getNamespaceUri())) {
                // the FS connector does not support namespaces in names
                String ns = newDocumentName.getNamespaceUri();
                getLogger().warn(JcrI18n.fileConnectorNamespaceIgnored, getSourceName(), ns, id, childNameStr, parentId);
            }
        }
        return id.toString();
    }

    @Override
    public void updateDocument( DocumentChanges documentChanges ) {
        String id = documentChanges.getDocumentId();

        Document document = documentChanges.getDocument();
        DocumentReader reader = readDocument(document);

        File file = fileFor(id);
        String idOrig = id;

        // if we're dealing with the root of the connector, we can't process any moves/removes because that would go "outside" the
        // connector scope
        if (!isRoot(id)) {
            String newParentId = reader.getParentIds().get(0);
            File parent = file.getParentFile();
            String oldParentId = idFor(parent);
            if (!oldParentId.equals(newParentId)) {
                // The node has a new parent (via the 'update' method), meaning it was moved ...
                File newParent = fileFor(newParentId);
                File newFile = new File(newParent, file.getName());
                if (!newParent.exists()) {
                    parent.mkdirs(); // in case they were removed since we created them ...
                }
                if (!newParent.canWrite()) {
                    String parentPath = newParent.getAbsolutePath();
                    String msg = JcrI18n.fileConnectorCannotWriteToDirectory.text(getSourceName(), getClass(), parentPath);
                    throw new DocumentStoreException(id, msg);
                }
                if (!file.renameTo(newFile)) {
                    getLogger().debug("Cannot move {0} to {1}", file.getAbsolutePath(), newFile.getAbsolutePath());
                } else {
                    id = idFor(newFile);
                    // Make sure any existing extra properties are also kept up-to-date
                    // Note that if the children ar folders, we don't need to walk them recursively because the node id will
                    // reflect the new folder structure of the rename
                    moveExtraProperties(idOrig, id);
                }
            } else {
                // It is the same parent as before ...
                if (!parent.exists()) {
                    parent.mkdirs(); // in case they were removed since we created them ...
                }
                if (!parent.canWrite()) {
                    String parentPath = parent.getAbsolutePath();
                    String msg = JcrI18n.fileConnectorCannotWriteToDirectory.text(getSourceName(), getClass(), parentPath);
                    throw new DocumentStoreException(id, msg);
                }
            }
        }

        // children renames have to be processed in the parent
        DocumentChanges.ChildrenChanges childrenChanges = documentChanges.getChildrenChanges();
        Map<String, Name> renamedChildren = childrenChanges.getRenamed();
        for (String renamedChildId : renamedChildren.keySet()) {
            File child = fileFor(renamedChildId);
            Name newName = renamedChildren.get(renamedChildId);
            String newNameStr = getContext().getValueFactories().getStringFactory().create(newName);
            File renamedChild = new File(file, newNameStr);
            if (!child.renameTo(renamedChild)) {
                getLogger().debug("Cannot rename {0} to {1}", child, renamedChild);
            } else {
                // Make sure any existing extra properties are also kept up-to-date
                // Note that if the children ar folders, we don't need to walk them recursively because the node id will reflect
                // the new folder structure of the rename
                moveExtraProperties(idFor(child), idFor(renamedChild));
            }
        }

        String primaryType = reader.getPrimaryTypeName();
        Map<Name, Property> properties = reader.getProperties();
        id = idOrig;
        ExtraProperties extraProperties = extraPropertiesFor(id, true);
        extraProperties.addAll(properties).except(JCR_PRIMARY_TYPE, JCR_CREATED, JCR_LAST_MODIFIED, JCR_DATA);
        try {
            if (NT_FILE.equals(primaryType)) {
                file.createNewFile();
            } else if (NT_FOLDER.equals(primaryType)) {
                file.mkdir();
            } else if (isContentNode(id)) {
                Property content = reader.getProperty(JCR_DATA);
                BinaryValue binary = factories().getBinaryFactory().create(content.getFirstValue());
                OutputStream ostream = new BufferedOutputStream(new FileOutputStream(file));
                IoUtil.write(binary.getStream(), ostream);
                if (!NT_RESOURCE.equals(primaryType)) {
                    // This is the "jcr:content" child, but the primary type is non-standard so record it as an extra property
                    extraProperties.add(properties.get(JcrLexicon.PRIMARY_TYPE));
                }
            }
            extraProperties.save();
        } catch (RepositoryException | IOException e) {
            throw new DocumentStoreException(id, e);
        }
    }

    @Override
    public Document getChildren( PageKey pageKey ) {
        String parentId = pageKey.getParentId();
        File folder = fileFor(parentId);
        assert folder.isDirectory();
        if (!folder.canRead()) {
            getLogger().debug("Cannot read the {0} folder", folder.getAbsolutePath());
            return null;
        }
        return newFolderWriter(parentId, folder, pageKey.getOffsetInt()).document();
    }

    private static class MonitoringTask implements Callable<Void> {
        protected final static WatchEvent.Kind<?>[] EVENTS_TO_WATCH = new WatchEvent.Kind<?>[] {ENTRY_CREATE, ENTRY_MODIFY,
            ENTRY_DELETE};
        protected final static java.nio.file.WatchEvent.Modifier WATCH_MODIFIER;

        private final WatchService watchService;
        private final FileSystemConnector connector;

        static {
            java.nio.file.WatchEvent.Modifier modifier = null;
            try {
                @SuppressWarnings( "unchecked" )
                Class<? extends Enum<?>> modifierClass = (Class<? extends Enum<?>>)Class.forName("com.sun.nio.file.SensitivityWatchEventModifier",
                                                                                false,
                                                                                FileSystemConnector.class.getClassLoader());
                for (Enum<?> enumConstants : modifierClass.getEnumConstants()) {
                    if (enumConstants.name().equalsIgnoreCase("HIGH")) {
                        modifier = (WatchEvent.Modifier)enumConstants;
                        break;
                    }
                }
            } catch (ClassNotFoundException e) {
                // modifier not available on this JDK
            }
            WATCH_MODIFIER = modifier;
        }

        protected MonitoringTask( WatchService watchService,
                                  FileSystemConnector connector,
                                  Path rootPath ) {
            this.watchService = watchService;
            this.connector = connector;
            recursiveWatch(rootPath, watchService);
        }

        @Override
        @SuppressWarnings( "unchecked" )
        public Void call() throws Exception {
            for (;;) {
                try {
                    WatchKey watchKey = watchService.take();
                    @SuppressWarnings( "synthetic-access" )
                    ConnectorChangeSet connectorChangeSet = connector.newConnectorChangedSet();
                    for (WatchEvent<?> watchEvent : watchKey.pollEvents()) {
                        WatchEvent.Kind<?> kind = watchEvent.kind();

                        Path eventPath = ((WatchEvent<Path>)watchEvent).context();
                        Path resolvedPath = ((Path)watchKey.watchable()).resolve(eventPath);
                        File resolvedFile = resolvedPath.toFile();

                        if (connector.isExcluded(resolvedFile)) {
                            continue;
                        }

                        if (kind == ENTRY_CREATE) {
                            fireEntryCreated(connectorChangeSet, resolvedPath);
                        } else if (kind == ENTRY_DELETE) {
                            fireEntryDeleted(connectorChangeSet, resolvedPath);
                        } else if (kind == ENTRY_MODIFY) {
                            fireEntryModified(connectorChangeSet, resolvedPath);
                        }
                    }
                    watchKey.reset();
                    connectorChangeSet.publish(null);
                } catch (InterruptedException e) {
                    Thread.interrupted();
                    watchService.close();
                    break;
                }
            }
            return null;
        }

        @SuppressWarnings( "synthetic-access" )
        private void fireEntryModified( ConnectorChangeSet connectorChangeSet,
                                        Path resolvedPath ) {
            connector.log().debug("Received ENTRY_MODIFY event on '{0}'", resolvedPath);

            // this event is *very much dependent on the OS*, so we'll try to focus on the most general case
            // we only handle file modifications and fire events for last_modified and content
            boolean isFile = Files.isRegularFile(resolvedPath, LinkOption.NOFOLLOW_LINKS);
            if (!isFile) {
                connector.log().debug("The entry at {0} is not a regular file; ignoring modify event", resolvedPath);
                return;
            }

            File file = resolvedPath.toFile();
            String contentId = connector.contentChildId(connector.idFor(file), false);
            boolean queryable = connector.isQueryable();

            // fire PROPERTY_CHANGED for nt:file/jcr:content/jcr:data/jcr:lastModified
            Property modifiedProperty = connector.propertyFactory().create(JcrLexicon.LAST_MODIFIED,
                                                                           connector.lastModifiedTimeFor(file));
            // there is no way to observe the previous value, so fire <null>
            connectorChangeSet.propertyChanged(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(), contentId, null,
                                               modifiedProperty);

            // fire PROPERTY_CHANGED for nt:file/jcr:content/jcr:data/jcr:lastModifiedBy
            String owner = connector.ownerFor(file);
            if (owner != null) {
                Property modifiedByProperty = connector.propertyFactory().create(JcrLexicon.LAST_MODIFIED_BY, owner);
                // there is no way to observe the previous value, so fire <null>
                connectorChangeSet.propertyChanged(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(), contentId,
                                                   null, modifiedByProperty);
            }

            try {
                // fire PROPERTY_CHANGED for nt:file/jcr:content/jcr:data
                BinaryValue binaryValue = connector.binaryFor(file);
                Property binaryProperty = connector.propertyFactory().create(JcrLexicon.DATA, binaryValue);
                connectorChangeSet.propertyChanged(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(), contentId,
                                                   null, binaryProperty);

                if (connector.addMimeTypeMixin) {
                    try {
                        // fire PROPERTY_CHANGED for nt:file/jcr:content/jcr:mimetype
                        String mimeType = binaryValue.getMimeType();
                        Property mimeTypeProperty = connector.propertyFactory().create(JcrLexicon.MIMETYPE, mimeType);
                        connectorChangeSet.propertyChanged(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(),
                                                           contentId, null, mimeTypeProperty);

                    } catch (Throwable e) {
                        connector.getLogger().error(e, JcrI18n.couldNotGetMimeType, connector.getSourceName(), contentId,
                                                    e.getMessage());
                    }
                }
            } catch (Exception e) {
                connector.log().warn("Cannot get binary value for '{0}'", resolvedPath);
            }
        }

        private void fireEntryDeleted( ConnectorChangeSet connectorChangeSet,
                                       Path resolvedPath ) {
            connector.log().debug("Received ENTRY_DELETE event on '{0}'", resolvedPath);

            boolean queryable = connector.isQueryable();
            Name primaryType = primaryTypeFor(resolvedPath);
            if (primaryType == null) {
                // Atm when a deleted event is received, because the item is no longer accessible on the FS.
                // it's neither a file nor a folder. So we do a "best effort" and use the base type
                primaryType = JcrNtLexicon.HIERARCHY_NODE;
            }
            File deletedFile = resolvedPath.toFile();
            String id = connector.idFor(deletedFile);
            Path parentPath = resolvedPath.getParent();
            Name parentPrimaryType = primaryTypeFor(parentPath);
            if (parentPrimaryType == null) {
                // Atm when a deleted event is received, because the item is no longer accessible on the FS.
                // it's neither a file nor a folder. So we do a "best effort" and use the base type
                parentPrimaryType = JcrNtLexicon.HIERARCHY_NODE;
            }

            connectorChangeSet.nodeRemoved(id, connector.idFor(parentPath.toFile()), id, primaryType,
                                           Collections.<Name>emptySet(), parentPrimaryType,
                                           Collections.<Name>emptySet());
        }

        @SuppressWarnings( "synthetic-access" )
        private void fireEntryCreated( ConnectorChangeSet connectorChangeSet,
                                       Path resolvedPath ) {
            connector.log().debug("Received ENTRY_CREATE event on '{0}'", resolvedPath);

            Name primaryType = primaryTypeFor(resolvedPath);
            if (primaryType == null) {
                return;
            }
            if (Files.isDirectory(resolvedPath, LinkOption.NOFOLLOW_LINKS)) {
                // if a new directory has been created, watch it
                recursiveWatch(resolvedPath, watchService);
            }

            File file = resolvedPath.toFile();
            String docId = connector.idFor(file);
            boolean queryable = connector.isQueryable();
            // fire NODE_ADDED for nt:file
            connectorChangeSet.nodeCreated(docId, connector.idFor(resolvedPath.getParent().toFile()), docId, primaryType,
                                           Collections.<Name>emptySet(), Collections.<Name, Property>emptyMap());
            // fire PROPERTY_ADDED for nt:file/jcr:created
            Property createdProperty = connector.propertyFactory().create(JcrLexicon.CREATED, connector.createdTimeFor(file));
            connectorChangeSet.propertyAdded(docId, primaryType, Collections.<Name>emptySet(), docId, createdProperty);

            String owner = connector.ownerFor(file);
            if (owner != null) {
                // fire PROPERTY_ADDED for nt:file/jcr:createdBy
                Property createdByProperty = connector.propertyFactory().create(JcrLexicon.CREATED_BY, owner);
                connectorChangeSet.propertyAdded(docId, primaryType, Collections.<Name>emptySet(), docId, createdByProperty
                                                );
            }
            if (Files.isRegularFile(resolvedPath, LinkOption.NOFOLLOW_LINKS)) {
                // for files we need to fire extra events for their content
                String contentId = connector.contentChildId(docId, false);
                // fire NODE_ADDED for the nt:file/jcr:content
                connectorChangeSet.nodeCreated(contentId, docId, contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(),
                                               Collections.<Name, Property>emptyMap());
                try {
                    // fire PROPERTY_ADDED for nt:file/jcr:content/jcr:data
                    BinaryValue binaryValue = connector.binaryFor(file);
                    Property dataProperty = connector.propertyFactory().create(JcrLexicon.DATA, binaryValue);
                    connectorChangeSet.propertyAdded(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(), contentId,
                                                     dataProperty);
                    if (connector.addMimeTypeMixin) {
                        try {
                            // fire PROPERTY_ADDED for nt:file/jcr:content/jcr:mimetype
                            String mimeType = binaryValue.getMimeType();
                            Property mimeTypeProperty = connector.propertyFactory().create(JcrLexicon.MIMETYPE, mimeType);
                            connectorChangeSet.propertyAdded(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(),
                                                             contentId, mimeTypeProperty);

                        } catch (Throwable e) {
                            connector.getLogger().error(e, JcrI18n.couldNotGetMimeType, connector.getSourceName(), contentId,
                                                        e.getMessage());
                        }
                    }
                } catch (Exception e) {
                    connector.log().warn("Cannot get binary value for '{0}'", resolvedPath);
                }

                // fire PROPERTY_ADDED for nt:file/jcr:content/jcr:lastModified
                Property lastModified = connector.propertyFactory().create(JcrLexicon.LAST_MODIFIED,
                                                                           connector.lastModifiedTimeFor(file));
                connectorChangeSet.propertyAdded(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(), contentId,
                                                 lastModified);
                if (owner != null) {
                    // fire PROPERTY_ADDED for nt:file/jcr:content/jcr:lastModifiedBy
                    Property lastModifiedBy = connector.propertyFactory().create(JcrLexicon.LAST_MODIFIED_BY, owner);
                    connectorChangeSet.propertyAdded(contentId, JcrNtLexicon.RESOURCE, Collections.<Name>emptySet(), contentId,
                                                     lastModifiedBy);
                }
            }
        }

        private void recursiveWatch( Path path,
                                     final WatchService watchService ) {
            connector.log().debug("Recursively watching '{0}'", path);
            try {
                Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult preVisitDirectory( Path dir,
                                                              BasicFileAttributes attrs ) throws IOException {
                        if (WATCH_MODIFIER != null) {
                            dir.register(watchService, EVENTS_TO_WATCH, WATCH_MODIFIER);
                        } else {
                            dir.register(watchService, EVENTS_TO_WATCH);
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private Name primaryTypeFor( Path resolvedPath ) {
            boolean isFolder = Files.isDirectory(resolvedPath, LinkOption.NOFOLLOW_LINKS);
            boolean isFile = Files.isRegularFile(resolvedPath, LinkOption.NOFOLLOW_LINKS);
            if (!isFile && !isFolder) {
                connector.log().debug("The entry at {0} is neither a file nor a folder", resolvedPath);
                return null;
            }
            return isFolder ? JcrNtLexicon.FOLDER : JcrNtLexicon.FILE;
        }
    }
}


text:
var teamSelector = function teamSelector(isPerson, obj) {

  this.isPerson = isPerson;
  this.selector = $(obj);
  this.orgBrowser = this.selector.find('.org-browser');
  this.editButton = this.selector.find('a.show-editable-fields');
  this.newTeam = this.selector.find('.new-team');
  this.newTeamInput = this.newTeam.find('.new-team-name');
  this.originalTeamLedQuestion = null;

  /* Listen for events */

  this.initEvents = function() {

    var self = this;
    if (this.isPerson) {
      teamName = this.selector.find('.editable-summary ol li:last-child').text();
      self.setTeamName(teamName);
    }

    /* Clicking the 'Change team' link to show the team selector */

    this.selector.on('click', '.show-editable-fields', function(e) {

      self.onClick(e);
      self.editButton.hide();
      self.selector.find('.editable-fields').show();
      self.init();
    });

    /* Clicking the 'Done' button to hide the team selector */

    this.selector.on('click', '.hide-editable-fields', function(e) {

      self.onClick(e);
      self.editButton.show();
      self.selector.find('.editable-fields').hide();
      self.selector.find('.team-leader label:first').focus();
    });

    /* Clicking on the 'Back' link */

    this.orgBrowser.on('click', '.team-back', function(e) {

      self.onClick(e);
      self.currentTarget = $(e.currentTarget);
      self.back();
    });

    /* Clicking on a team with subteams */

    this.orgBrowser.on('click', 'li:not(.disabled) .subteam-link', function(e) {

      self.onClick(e);
      self.currentTarget = $(e.currentTarget);
      self.forward();
    });

    /* Clicking on a team with no subteams */

    this.orgBrowser.on('click', '.team-link', function(e) {

      self.onClick(e);
      self.currentTarget = $(e.currentTarget);
      // if it's a form
      if (self.orgBrowser.hasClass('has-form')) {
        e.preventDefault();
        e.stopPropagation();
        self.currentTarget.closest('h3').children('input').prop('checked', 'checked').trigger('change');
      }
    });

    /* Clicking directly on a radio button */

    this.orgBrowser.on('change', 'input[type=radio]', function(e) {

      self.currentTarget = $(e.currentTarget);
      self.currentTarget.focus();
      self.setTeamName(self.getTeamName(self.currentTarget));
      var isSubTeam = self.currentTarget.next('a').hasClass('subteam-link') ? true : false;
      self.getBreadcrumb(isSubTeam);
    });

    this.orgBrowser.on('keypress, keydown', function(e) {
      var keyCode = e.keyCode || e.which || e.charCode,
        target = $(e.target),
        top = target.parent().prev().hasClass('team-back');
      switch (keyCode) {
        case 9: //Tab
        case 13: // Enter
          self.selector.find('.hide-editable-fields').focus();
          break;
        case 37: //Left
        case 38: // Up

          if (top) {
            self.currentTarget = target.parent().prev();
            self.back();
            return false;
          } else {
            var li = target.closest('li').prev('li');

            if (li.hasClass('has-subteams')) {
              li.find('> p > .subteam-link').focus();
              return false;
            } else if (li.hasClass('leaf-node')) {
              li.find('>p>input').prop('checked', 'checked').trigger('change').focus();
              return false;
            }
          }
          break;
        case 40: // Down

          if (top) {
            var subTeam = target.parent().next('ul');
            if (subTeam.length > 0) {
              var li = subTeam.find('li').first();
              if (li.hasClass('leaf-node')) {
                li.find('>p>input').prop('checked', 'checked').trigger('change').focus();
                return false;
              } else {
                li.find('> p > .subteam-link').focus();
              }
              return false;
            }
          } else {
            var li = target.closest('li').next('li');

            if (li.hasClass('leaf-node')) {
              li.find('>p>input').prop('checked', 'checked').trigger('change').focus();
              return false;
            } else {
              li.find('.subteam-link').focus();
              return false;
            }
          }
          break;
        case 39: // Right

          if (target.hasClass('subteam-link')) {
            self.currentTarget = target;
            self.forward();
          }
          return false;
          break;
        default:
          null;
      }
    });

    /* Add New Team */

    this.selector.on('click', '.button-add-team', function(e) {
      self.onClick(e);
      self.toggleTeamInput(true);
    });
    this.newTeam.on('click', '.button', function(e) {
      self.onClick(e);
      self.createNewTeam();
    });
    this.newTeamInput.on('keypress', function(e) {
      var keyCode = e.keyCode || e.which;
      if (keyCode === 13) {
        self.onClick(e);
        self.createNewTeam();
      }
    });
  };

  /* Handle clicks and preventDefault and stopPropagation */

  this.onClick = function(e) {
    e.preventDefault();
    e.stopPropagation();
  };

  /* Set the 'selected' class on the last visible team */

  this.init = function() {

    var self = this;
    if (this.orgBrowser.hasClass('has-form')) {
      var $checked = this.orgBrowser.find('input:checked');
      if ($checked.length > 0) {
        $checked.parents('.team').addClass('visible');
        $checked.focus();
      }
    }
    this.setExpanded();
    // Hide all team headings (for testing purposes)
    this.orgBrowser.find('h3 > a').hide();
    this.removeSelected();
    var visible = this.getLastVisible().addClass('selected').find('> h3 > a').show();

    setTimeout(function() {
      self.animateScroll();
    }, 0);
  };

  /*
  Remove the 'expanded' class from current subteams
  Scroll backwards and wait before removing the 'visible' class
  */

  this.back = function() {

    var self = this,
      team = this.currentTarget.parent('.team');
    this.currentTarget.children('li').removeClass('expanded');
    this.animateScroll('left');
    // Wait for the scroll back to complete

    setTimeout(function() {
      self.orgBrowser.find('.visible > h3 > a').hide();
      team.removeClass('visible');
      self.selectCurrent();
    }, 400);
  };

  /* If the team has subteams, find the next team */

  this.forward = function() {

    if (this.currentTarget.closest('li').hasClass('has-subteams') === false) {
      if (this.orgBrowser.hasClass('has-form')) {
        var input = this.currentTarget.closest('p').children('input').prop('checked', 'checked').trigger('change');
        this.setTeamName(this.getTeamName(input));
      }
      // if this is a leaf-node, we let the link work as it normally would
      return;
    }
    var team = this.currentTarget.closest('p').siblings('.team');
    this.revealSubteam(team);
    this.selectCurrent();
    this.animateScroll('right');
  };

  this.getBreadcrumb = function(isSubTeam) {

    var arr = [],
      selector = isSubTeam ? '.team.visible' : '.team.visible:not(.selected)';
    this.orgBrowser.find(selector).each(function(i, obj) {
      var text = $(obj).find('>h3>a').text();
      arr.push(text);
    });
    arr.push(this.getTeamName(this.currentTarget));

    this.orgBrowser.closest('.editable-container').find('.editable-summary .title .breadcrumbs').html(this.createBreadcrumb(arr));
  };

  this.createBreadcrumb = function(arr) {
    var ol = $('
');
    $(arr).each(function(i, crumb) {
      var li = $('
').addClass('breadcrumb-' + i).text($.trim(crumb));
      ol.append(li);
    });

    return $(ol);
  };

  /* Return the text of the given input element */

  this.getTeamName = function(input) {
    return input.next('a').text();
  };

  /* Set the Team leader heading and hint spans with the given team name */

  this.setTeamName = function(teamName) {

    if (this.isPerson) {
      if (this.originalTeamLedQuestion) {
        this.selector.find('.team-leader legend').html(this.originalTeamLedQuestion);
        this.originalTeamLedQuestion = null;
      }
      this.selector.find('.team-led').text(teamName + ' team');

      if (teamName === 'Ministry of Justice') {
        var legend = this.selector.find('.team-leader legend');
        this.originalTeamLedQuestion = legend.html();
        if (legend.text().indexOf('you')) {
          legend.text('Are you the Permanent Secretary?');
        } else {
          legend.text('Is this person the Permanent Secretary?');
        }
      }
    }
  };

  /* Remove the 'selected' class from all teams */

  this.removeSelected = function() {

    this.orgBrowser.find('.team').removeClass('selected');
  };

  /* Return the last 'visible' team element */

  this.getLastVisible = function() {

    return this.orgBrowser.find('.visible').last();
  };

  /* Set the last 'visible' elements parent li to have the 'expanded' class */

  this.setExpanded = function() {

    this.orgBrowser.find('.visible').parents('li').addClass('expanded');
  };

  /* When moving forward down the subteams */

  this.revealSubteam = function(team) {

    this.setExpanded();
    // Remove the 'visible' class from all teams
    this.orgBrowser.find('.team').removeClass('visible');
    team.children().parents('.team').addClass('visible');
  };

  /* Set the last 'visible' element to be 'selected' and check the radio button, then set the team name */

  this.selectCurrent = function() {
    var teamText, $current;
    this.removeSelected();
    var visible = this.getLastVisible();
    visible.addClass('selected').find('> h3 > input').prop('checked', 'checked').trigger('change');
    visible.find('> h3 > a').show();
  };

  /* Animate the team selector to scroll left or right */

  this.animateScroll = function(direction) {

    var visible = this.orgBrowser.find('.visible');
    var offset = direction === 'left' ? (visible.length - 2) * visible.width() : visible.length * visible.width();
    this.orgBrowser.animate({
      scrollLeft: offset
    }, 400);
  };

  this.toggleTeamInput = function(focus) {

    this.newTeam.toggle();
    if (focus)
      this.newTeamInput.val('').focus();
  };

  this.createNewTeam = function() {

    var self = this,
      input = this.orgBrowser.find('input:checked'),
      teamId = input.val(),
      teamName = input.next('a').text(),
      newTeamName = this.newTeamInput.val();
    if (teamId && newTeamName) {
      $.ajax({
        type: 'POST',
        dataType: 'json',
        url: '/teams',
        data: {
          group: {
            parent_id: teamId,
            name: newTeamName
          }
        },
        success: function(data) {
          data = {
            id: data.id,
            name: data.name,
            parentId: data.parent_id,
            parentName: teamName
          };
          self.addTeamToList(data);
        },
        error: function() {
          self.orgBrowser.before('

There was an error adding the team. Please try again later.

');
        }
      });
    }
  };

  this.createInput = function(i, id) {

    return '<input type="radio" value="' + id + '" name="person[memberships_attributes][' + i + '][group_id]" id="person_memberships_attributes_' + i + '_group_id_' + id + '">';
  };

  this.createTeamName = function(data) {

    return [
      '',
      '' + data.name + '',
      ''
    ].join('');
  };

  this.createTeamList = function(i, data) {
    return ['
',
      'Back',
      '
',
      this.createInput(i, data.parentId),
      '' + data.parentName + '',
      '
',
      '
',
      '
',
      '
',
      '<input type="radio" value="' + data.id + '" name="person[memberships_attributes][' + i + '][group_id]" id="person_memberships_attributes_' + i + '_group_id_' + data.id + '">',
      this.createTeamName(data),
      '
',
      '
',
      '
',
      '
'
    ].join('');
  };

  this.addTeamToList = function(data) {

    var self = this,
      isSubteam = this.orgBrowser.find('input:checked').next().hasClass('subteam-link') ? true : false,
      input = isSubteam ? $('input[value="' + data.parentId + '"]').closest('li') : $('input[value="' + data.parentId + '"]').parent('h3').next('ul');

    $.each(input, function(i, obj) {
      if (isSubteam) {
        self.currentTarget = self.orgBrowser.find('input:checked').next('.subteam-link');
        var el = self.createTeamList(i, data);
        $(obj).find('.subteam-link').append('1 sub-team');
        $(obj).attr('class', 'has-subteams').append(el);
        self.forward();
      } else {
        var el = '
' +

          self.createInput(i, data.id) + self.createTeamName(data) + '
';
        var li = $('
').addClass('leaf-node').html(el);
        $(obj).append(li);
      }
    });
    this.toggleTeamInput();
  };

};

$(function() {

  // Is this the person profile page?
  var isPerson = $('#memberships').length === 1 ? true : false;
  // Which element should we be targeting?
  var selector = isPerson ? '#memberships .membership' : '.editable-container';
  // For each element, set the team name on team leader text and create a new teamSelector
  $(selector).each(function(i, obj) {
    $(obj).addClass('index' + i);
    var team = new teamSelector(isPerson, obj);
    team.initEvents();
  });
});

redpajama_set_name: RedPajamaGithub


text:
require.config({
    paths: {
        'rteadapter': 'src/tb/apps/content/plugins/rte/adapter/rteadapter'
    }
});
define(['content.pluginmanager', 'component!rtemanager', 'Core', 'jquery', 'jsclass'], function (PluginManager, RteManager, Core, jQuery) {
    'use strict';
    PluginManager.registerPlugin("rte", {
        scope: PluginManager.scope.CONTENT,
        onInit: function () {
            this.readyDfd = new jQuery.Deferred();
            this.adapterIsLoaded = false;
        },

        loadRte: function () {
            var self = this,
                adapter = this.getConfig("adapter");
            RteManager.use(adapter);
            Core.Mediator.subscribe("rte:rteAdapter:isReady", function (adaptor) {
                self.adapterIsLoaded = true;
                self.rteAdapter = adaptor;
                self.readyDfd.resolve();
            });
        },

        onDisable: function () {
            this.callSuper();
            if (this.rteAdapter) {
                this.rteAdapter.disable();
            }
        },

        onEnable: function () {
            var self = this;
            this.callSuper();
            this.loadRte();
            if (!this.rteAdapter && this.readyDfd) {
                this.readyDfd.done(function () {
                    self.rteAdapter.applyToContent(self.getCurrentContent());
                });
            } else {
                self.rteAdapter.applyToContent(self.getCurrentContent());
            }
        },

        canApplyOnContext: function () {
            return this.context.scope === "contribution.content";
        }
    });
});


text:
package org.apache.camel.processor;

import org.apache.camel.ContextTestSupport;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.junit.Test;
import org.slf4j.MDC;

/**
 * //TODO: check mdc
 */
public class MDCWireTapTest extends ContextTestSupport {

    @Test
    public void testMDC() throws Exception {
        getMockEndpoint("mock:a").expectedMessageCount(1);
        getMockEndpoint("mock:b").expectedMessageCount(1);

        template.sendBody("direct:a", "Hello World");

        assertMockEndpointsSatisfied();
    }

    @Override
    protected RouteBuilder createRouteBuilder() throws Exception {
        return new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                // enable MDC
                context.setUseMDCLogging(true);

                from("direct:a").routeId("route-a")
                    .process(new Processor() {
                        public void process(Exchange exchange) throws Exception {
                            assertEquals("route-a", MDC.get("camel.routeId"));
                            assertEquals(exchange.getExchangeId(), MDC.get("camel.exchangeId"));

                            //MDC.put("custom.id", "1");
                        }
                    })
                    .to("log:before-wiretap")
                    .wireTap("direct:b")
                    .delay(2000)
                        .process(new Processor() {
                            public void process(Exchange exchange) throws Exception {
                                assertEquals("route-a", MDC.get("camel.routeId"));
                                assertEquals(exchange.getExchangeId(), MDC.get("camel.exchangeId"));
                                //assertEquals("1", MDC.get("custom.id"));
                            }
                        })
                    .to("log:a-done")
                    .to("mock:a");

                from("direct:b").routeId("route-b")
                        .process(new Processor() {
                            public void process(Exchange exchange) throws Exception {
                                assertEquals("route-b", MDC.get("camel.routeId"));
                                assertEquals(exchange.getExchangeId(), MDC.get("camel.exchangeId"));
                                //assertEquals("1", MDC.get("custom.id"));
                            }
                        })
                    .to("log:b-done")
                    .to("mock:b");
            }
        };
    }
}


text:
action :create do
  host = new_resource.auth_host
  port = new_resource.auth_port
  protocol = new_resource.auth_protocol
  token = new_resource.auth_token
  api_ver = new_resource.api_ver
  svc_type = new_resource.service_type
  svc_name = new_resource.service_name
  svc_desc = new_resource.service_description

  # construct a HTTP object
  http = Net::HTTP.new(host, port)

  # Check to see if connection is http or https
  if protocol == "https"
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  # Build out the required header info
  headers = build_headers(token)

  # Construct the extension path
  path = "/#{api_ver}/OS-KSADM/services"

  # lookup service_uuid
  service_container = "OS-KSADM:services"
  service_key = "type"
  service_path = "/#{api_ver}/OS-KSADM/services"
  service_uuid, service_error = find_value(http, service_path, headers, service_container, service_key, svc_type, 'id')
  Chef::Log.error("There was an error looking up Service '#{svc_type}'") if service_error

  # See if the service exists yet
  unless service_uuid or service_error
    # Service does not exist yet
    payload = build_service_object(svc_type, svc_name, svc_desc)
    resp = http.send_request('POST', path, JSON.generate(payload), headers)
    if resp.is_a?(Net::HTTPOK)
      Chef::Log.info("Created service '#{svc_name}'")
      new_resource.updated_by_last_action(true)
    else
      Chef::Log.error("Unable to create service '#{svc_name}'")
      Chef::Log.error("Response Code: #{resp.code}")
      Chef::Log.error("Response Message: #{resp.message}")
      new_resource.updated_by_last_action(false)
    end
  else
    Chef::Log.info("Service Type '#{svc_type}' already exists.. Not creating.") if service_uuid
    Chef::Log.info("Service UUID: #{service_uuid}") if service_uuid
    Chef::Log.error("There was an error looking up '#{svc_type}'") if service_error
    new_resource.updated_by_last_action(false)
  end
end

action :delete do
  host = new_resource.auth_host
  port = new_resource.auth_port
  protocol = new_resource.auth_protocol
  token = new_resource.auth_token
  api_ver = new_resource.api_ver
  svc_type = new_resource.service_type
  svc_name = new_resource.service_name
  svc_desc = new_resource.service_description

  # construct a HTTP object
  http = Net::HTTP.new(host, port)

  # Check to see if connection is http or https
  if protocol == "https"
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  # Build out the required header info
  headers = build_headers(token)

  # Construct the extension path
  path = "/#{api_ver}/OS-KSADM/services"

  # lookup service_uuid
  service_container = "OS-KSADM:services"
  service_key = "type"
  service_path = "/#{api_ver}/OS-KSADM/services"
  service_uuid, service_error = find_value(http, service_path, headers, service_container, service_key, svc_type, 'id')
  Chef::Log.error("There was an error looking up Service '#{svc_type}'") if service_error

  # See if the service exists yet
  if service_uuid
    unless service_error
      path = "#{path}/#{service_uuid}"
      resp = http.delete(path, headers)
      if resp.is_a?(Net::HTTPNoContent)
        Chef::Log.info("Deleted service '#{svc_name}'")
        new_resource.updated_by_last_action(true)
      else
        Chef::Log.error("Unable to delete service '#{svc_name}'")
        Chef::Log.error("Response Code: #{resp.code}")
        Chef::Log.error("Response Message: #{resp.message}")
        new_resource.updated_by_last_action(false)
      end
    end
  else
    Chef::Log.info("Service Type '#{svc_type}' does not exist.. Can't delete.") if not service_uuid
    Chef::Log.error("There was an error looking up '#{svc_type}'") if service_error
    new_resource.updated_by_last_action(false)
  end
end


text:
require 'spec_helper'
require 'cloudbricks/base_plate'
require 'cloudbricks/define'

describe CloudBricks::BasePlate do
  describe '#create' do
    subject {
      module CloudBricks
        BasePlate.create {
          brick('test') {}
        }
      end
    }

    it 'should be callable' do
      expect { subject }.not_to raise_error
    end

    it 'should memorize inner Base objects' do
      expect(CloudBricks::BasePlate.objects).not_to be_empty
    end
  end
end


text:
package be.peopleware.value_II;

import java.io.Serializable;
import java.util.Locale;
import java.util.Map;

/**
 * @author    Jan Dockx
 * @author    PeopleWare n.v.
 */
public class CountryEditor
    extends DisplayLocaleBasedEnumerationValueEditor implements Serializable {

  /*
*/
  //------------------------------------------------------------------

  /** {@value} */
  public static final String CVS_REVISION = "$Revision$"; //$NON-NLS-1$
  /** {@value} */
  public static final String CVS_DATE = "$Date$"; //$NON-NLS-1$
  /** {@value} */
  public static final String CVS_STATE = "$State$"; //$NON-NLS-1$
  /** {@value} */
  public static final String CVS_TAG = "$Name$"; //$NON-NLS-1$

  /*
*/

  /**
   * @return    Locale.class;
   */
  public final Class getEnumerationValueType() {
    return Country.class;
  }

  public final Map getValuesMap() {
    return Country.VALUES;
  }

  /**
   * The name of the country in the language of {@link #getDisplayLocale()}.
   * If there is no display locale, the name of the country in English.
   *
   * @return    new Locale("en", toString())
   *                .getDisplayCountry((getDisplayLocale() == null)
   *                                      ? new Locale("en", toString())
   *                                      : getDisplayLocale());
   *
   * @idea (jand): Try to find a way to return, as default, the name of the
   *               country in its main language. although, that is political.
   */
  public final String getLabel() {
    String result = ""; //$NON-NLS-1$
    if ((getValue() != null) && (getValue() instanceof Country)) {
      Country country = (Country)getValue();
      Locale localeToShow = new Locale("en", country.toString()); //$NON-NLS-1$
            /* language is just a default (null is not accepted);
             * we are only interested in the country setting */
      result = localeToShow.getDisplayCountry((getDisplayLocale() == null)
                                                  ? localeToShow
                                                  : getDisplayLocale());
    }
    return result;
  }

}


text:
Shindo.tests('AWS::DNS | DNS requests', ['aws', 'dns']) do

  @org_zone_count = 0
  @zone_id = ''
  @change_id = ''
  @new_records =[]

  # NOTE: can't use generate_unique_domain() as we do in other DNS provider
  #       test suites as AWS charges $1/mth for each domain, even if it exists
  #       on AWS for only the time that this test suite runs!!
  #       http://aws.amazon.com/route53/pricing/
  @test_domain = 'test-343246324434.com'

  tests( 'success') do

    test('see if test domain already exists') do
      pending if Fog.mocking?

      @zone_id = nil

      response = AWS[:dns].list_hosted_zones()
      if response.status == 200
        @hosted_zones = response.body['HostedZones']
      end

      #go through zones for this account
      @hosted_zones.each { |zone|
        domain = zone['Name']
        if domain.chomp == @test_domain
          @zone_id = zone['Id']
        end
      }

      @zone_id.nil?
    end

    test('get current zone count') do
      pending if Fog.mocking?

      @org_zone_count= 0
      response = AWS[:dns].list_hosted_zones()
      if response.status == 200
        @hosted_zones = response.body['HostedZones']
        @org_zone_count = @hosted_zones.count
      end

      response.status == 200
    end

    test('create simple zone') {
      pending if Fog.mocking?

      result = false

      response = AWS[:dns].create_hosted_zone( @test_domain)
      if response.status == 201

        zone= response.body['HostedZone']
        change_info = response.body['ChangeInfo']
        ns_servers = response.body['NameServers']

        if (zone and change_info and ns_servers)

          @zone_id = zone['Id']
          caller_ref = zone['CallerReference']
          @change_id = change_info['Id']
          status = change_info['Status']
          ns_srv_count = ns_servers.count

          if (@zone_id.length > 0) and (caller_ref.length > 0) and (@change_id.length > 0) and
             (status.length > 0) and (ns_srv_count > 0)
            result = true
          end
        end
      end

      result
    }

    test("get status of change #{@change_id}") {
      pending if Fog.mocking?

      result = false
      response = AWS[:dns].get_change(@change_id)
      if response.status == 200
        status = response.body['Status']
        if (status == 'PENDING') or (status == 'INSYNC')
          result= true
        end
      end

      result
    }

    test("get info on hosted zone #{@zone_id}") {
      pending if Fog.mocking?

      result = false

      response = AWS[:dns].get_hosted_zone( @zone_id)
      if response.status == 200
        zone = response.body['HostedZone']
        zone_id = zone['Id']
        name = zone['Name']
        caller_ref = zone['CallerReference']
        ns_servers = response.body['NameServers']

        # AWS returns domain with a dot at end - so when compare, remove dot

        if (zone_id == @zone_id) and (name.chop == @test_domain) and (caller_ref.length > 0) and
           (ns_servers.count > 0)
           result = true
        end
      end

      result
    }

    test('list zones') do
      pending if Fog.mocking?

      result = false

      response = AWS[:dns].list_hosted_zones()
      if response.status == 200

        zones= response.body['HostedZones']
        if (zones.count > 0)
          zone= zones[0]
          zone_id = zone['Id']
          zone_name= zone['Name']
          caller_ref = zone['CallerReference']
        end
        max_items = response.body['MaxItems']

        if (zone_id.length > 0) and (zone_name.length > 0) and (caller_ref.length > 0) and
           (max_items > 0)
          result = true
        end
      end

      result
    end

    test("add a A resource record") {
      pending if Fog.mocking?

      result = false

      # create an A resource record
      host = 'www.' + @test_domain
      ip_addrs = ['1.2.3.4']
      resource_record = { :name => host, :type => 'A', :ttl => 3600, :resource_records => ip_addrs }
      resource_record_set = resource_record.merge( :action => 'CREATE')

      change_batch = []
      change_batch << resource_record_set
      options = { :comment => 'add A record to domain'}
      response = AWS[:dns].change_resource_record_sets( @zone_id, change_batch, options)
      if response.status == 200
        change_id = response.body['Id']
        status = response.body['Status']
        @new_records << resource_record
      end

      response.status == 200
    }

    test("add a CNAME resource record") {
      pending if Fog.mocking?

      result = false

      # create a CNAME resource record
      host = 'mail.' + @test_domain
      value = ['www.' + @test_domain]
      resource_record = { :name => host, :type => 'CNAME', :ttl => 3600, :resource_records => value }
      resource_record_set = resource_record.merge( :action => 'CREATE')

      change_batch = []
      change_batch << resource_record_set
      options = { :comment => 'add CNAME record to domain'}
      response = AWS[:dns].change_resource_record_sets( @zone_id, change_batch, options)
      if response.status == 200
        change_id = response.body['Id']
        status = response.body['Status']
        @new_records << resource_record
      end

      response.status == 200
    }

    test("add a MX resource record") {
      pending if Fog.mocking?

      result = false

      # create a MX resource record
      host = @test_domain
      value = ['7 mail.' + @test_domain]
      resource_record = { :name => host, :type => 'MX', :ttl => 3600, :resource_records => value }
      resource_record_set = resource_record.merge( :action => 'CREATE')

      change_batch = []
      change_batch << resource_record_set
      options = { :comment => 'add MX record to domain'}
      response = AWS[:dns].change_resource_record_sets( @zone_id, change_batch, options)
      if response.status == 200
        change_id = response.body['Id']
        status = response.body['Status']
        @new_records << resource_record
      end

      response.status == 200
    }

    test("list resource records")  {
      pending if Fog.mocking?

      # get resource records for zone
      response = AWS[:dns].list_resource_record_sets( @zone_id)
      if response.status == 200
        record_sets= response.body['ResourceRecordSets']
        num_records= record_sets.count
      end

      response.status == 200
    }

    test("delete #{@new_records.count} resource records") {
      pending if Fog.mocking?

      result = true

      change_batch = []
      @new_records.each { |record|
        resource_record_set = record.merge( :action => 'DELETE')
        change_batch << resource_record_set
      }
      options = { :comment => 'remove records from domain'}
      response = AWS[:dns].change_resource_record_sets( @zone_id, change_batch, options)
      if response.status != 200
        result = false
        break
      end

      result
    }

    test("delete hosted zone #{@zone_id}") {
      pending if Fog.mocking?

      response = AWS[:dns].delete_hosted_zone( @zone_id)

      response.status == 200
    }

  end

  tests( 'failure') do
    tests('create hosted zone using invalid domain name').raises(Excon::Errors::BadRequest) do
      pending if Fog.mocking?
      response = AWS[:dns].create_hosted_zone('invalid-domain')
    end

    tests('get hosted zone using invalid ID').raises(Excon::Errors::Forbidden) do
      pending if Fog.mocking?
      zone_id = 'dummy-id'
      response = AWS[:dns].get_hosted_zone(zone_id)
    end

  end

end


text:
'use strict';

const user = require.main.require('./src/user');
const topics = require.main.require('./src/topics');
const posts = require.main.require('./src/posts');
const privileges = require.main.require('./src/privileges');

const plugin = module.exports;

plugin.onTopicMove = async function (data) {
	if (!data) {
		return;
	}
	const isAdmin = await user.isAdministrator(data.uid);
	if (!isAdmin) {
		return;
	}
	const hiddenToVisible = await isHiddenToVisible(data.fromCid, data.toCid);
	if (hiddenToVisible) {
		await sendNotification(data.tid);
	}
};

async function sendNotification(tid) {
	const topicData = await topics.getTopicData(tid);
	if (!topicData) {
		return;
	}
	const postData = await posts.getPostData(topicData.mainPid);
	if (!postData) {
		return;
	}
	postData.user = await user.getUserFields(postData.uid, ['username']);
	await user.notifications.sendTopicNotificationToFollowers(postData.uid, topicData, postData);
}

async function isHiddenToVisible(fromCid, toCid) {
	const [group1, group2] = await Promise.all([
		checkGroup('Platinum Members', fromCid, toCid),
		checkGroup('Diamond Members', fromCid, toCid),
	]);
	return group1 || group2;
}

async function checkGroup(groupName, fromCid, toCid) {
	const [from, to] = await Promise.all([
		privileges.categories.groupPrivileges(fromCid, groupName),
		privileges.categories.groupPrivileges(toCid, groupName),
	]);
	return !from['groups:read'] && to['groups:read'];
}


text:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyCopyright("Copyright © Shannon Deminick 2015")]

[assembly: AssemblyCompany("Shannon Deminick")]

[assembly: AssemblyConfiguration("")]

[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("2.0.0")]
[assembly: AssemblyFileVersion("2.0.0")]
[assembly: AssemblyInformationalVersion("2.0.0")]


text:
title: Software, Component, and Service Deployment in Computational Grids
subtitle: ''
summary: ''
authors:
- Gregor von Laszewski
- Eric Blau
- Michael Bletzinger
- Jarek Gawor
- Peter Lane
- Stuart Martin
- Michael Russell
tags: []
categories: []
date: '2002-06-01'
lastmod: 2021-09-16T16:08:04-04:00
featured: false
draft: false

# Featured image
# To use, add an image named `featured.jpg/png` to your page's folder.
# Focal points: Smart, Center, TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight.
image:
  caption: ''
  focal_point: ''
  preview_only: false

# Projects (optional).
#   Associate this post with one or more of your projects.
#   Simply enter your project's folder or file name without extension.
#   E.g. `projects = ["internal-project"]` references `content/project/deep-learning/index.md`.
#   Otherwise, set `projects = []`.
projects: []
publishDate: '2021-09-16T20:08:04.250292Z'
publication_types:
- '1'
abstract: ''
publication: '*IFIP/ACM Working Conference on Component Deployment*'
url_pdf: https://laszewski.github.io/papers/vonLaszewski-deploy-32.pdf
---