Package org.apache.slide.webdav.method

Source Code of org.apache.slide.webdav.method.GetMethod

/*
* $Header: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/GetMethod.java,v 1.40.2.3 2004/02/09 07:27:51 ozeigermann Exp $
* $Revision: 1.40.2.3 $
* $Date: 2004/02/09 07:27:51 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* 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.apache.slide.webdav.method;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionContentNotFoundException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.structure.LinkedObjectNotFoundException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.util.Configuration;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.LabeledRevisionNotFoundException;
import org.apache.slide.webdav.util.PreconditionViolationException;
import org.apache.slide.webdav.util.VersioningHelper;
import org.apache.slide.webdav.util.ViolatedPrecondition;
import org.apache.slide.webdav.util.WebdavUtils;
import org.apache.util.WebdavStatus;

/**
* GET method.
*
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
*/
public class GetMethod extends AbstractWebdavMethod {


    // -------------------------------------------------------------- Constants


    protected final int BUFFER_SIZE = 2048;


    /**
     * The set of SimpleDateFormat formats to use in getDateHeader().
     */
    protected static final SimpleDateFormat formats[] = {
        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
            new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
            new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
    };


    /**
     * MIME multipart separation string
     */
    protected static final String mimeSeparation = "SLIDE_MIME_BOUNDARY";


    /**
     * The input buffer size to use when serving resources.
     */
    protected int input = 2048;


    /**
     * The output buffer size to use when serving resources.
     */
    protected int output = 2048;


    /**
     * Print content.
     */
    protected boolean printContent = true;

    /**
     * The VersioningHelper used by this instance.
     */
    protected VersioningHelper vHelp = null;


    // ----------------------------------------------------- Instance Variables


    /**
     * Resource to be retrieved.
     */
    protected String resourcePath;


    // ----------------------------------------------------------- Constructors


    /**
     * Constructor.
     *
     * @param token     the token for accessing the namespace
     * @param config    configuration of the WebDAV servlet
     */
    public GetMethod(NamespaceAccessToken token, WebdavServletConfig config) {
        super(token, config);
    }


    // ------------------------------------------------------ Protected Methods


    /**
     * Parse XML request.
     */
    protected void parseRequest()
        throws WebdavException {
        vHelp =  VersioningHelper.getVersioningHelper(
            slideToken, token, req, resp, getConfig() );
        resourcePath = requestUri;
        if (resourcePath == null) {
            resourcePath = "/";
        }

        // evaluate "Label" header
        if (Configuration.useVersionControl()) {
            try {

                String labelHeader = WebdavUtils.fixTomcatHeader(requestHeaders.getLabel(), "UTF-8");
                resourcePath = vHelp.getLabeledResourceUri(resourcePath, labelHeader);
            }
            catch (LabeledRevisionNotFoundException e) {
                ViolatedPrecondition violatedPrecondition =
                    new ViolatedPrecondition(DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY,
                                             WebdavStatus.SC_CONFLICT);
                try {
                    sendPreconditionViolation(new PreconditionViolationException(violatedPrecondition,
                                                                                 resourcePath));
                } catch (IOException ioe) {}
                throw new WebdavException( WebdavStatus.SC_CONFLICT );
            }
            catch (SlideException e) {
                int statusCode = getErrorCode( (Exception)e );
                sendError( statusCode, e );
                throw new WebdavException( statusCode );
            }
        }

    }


    /**
     * Execute request.
     *
     * @exception WebdavException Can't access resource
     */
    protected void executeRequest()
        throws WebdavException {
       
        // check lock-null resources
        try {
            if (isLockNull(resourcePath)) {
                int statusCode = WebdavStatus.SC_NOT_FOUND;
                sendError( statusCode, "lock-null resource", new Object[]{resourcePath} );
                throw new WebdavException( statusCode );
            }
        }
        catch (ServiceAccessException e) {
            int statusCode = getErrorCode((Exception)e);
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }

        try {

            // Then we must get object contents ...

            ObjectNode object = structure.retrieve(slideToken, resourcePath);
            NodeRevisionDescriptors revisionDescriptors =
                content.retrieve(slideToken, resourcePath);

            if (revisionDescriptors.hasRevisions()) {

                // Retrieve latest revision descriptor
                NodeRevisionDescriptor revisionDescriptor =
                    content.retrieve(slideToken, revisionDescriptors);

                if (revisionDescriptor != null) {

                    ResourceInfo resourceInfo =
                        new ResourceInfo(resourcePath, revisionDescriptor);

                    // Checking If headers
                    if (!checkIfHeaders(req, resp, resourceInfo))
                        return;

                    ServletOutputStream os = resp.getOutputStream();
                    InputStream         is = null;

                    if (printContent) {
                        is = content.retrieve
                            (slideToken, revisionDescriptors,
                             revisionDescriptor).streamContent();
                    }

                    Vector ranges = parseRange(req, resp, resourceInfo);

                    // ETag header
                    resp.setHeader("ETag", revisionDescriptor.getETag() );
                    resp.setHeader
                        ("Content-Language", revisionDescriptor.getContentLanguage());
                    resp.addHeader
                        ("Last-Modified",
                         revisionDescriptor.getLastModified().toString());

                    if ( ((ranges == null) || (ranges.isEmpty()))
                        && (req.getHeader("Range") == null) ) {

                        resp.setContentType
                            (revisionDescriptor.getContentType());
                        resp.setContentLength
                            ((int) revisionDescriptor.getContentLength());

                        // Copy the input stream to our output stream
                        // (if requested)
                        if (printContent) {
                            resp.setBufferSize(output);
                            copy(resourceInfo, is, os);
                        }

                    } else {

                        if ((ranges == null) || (ranges.isEmpty()))
                            return;

                        // Partial content response.

                        resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

                        if (ranges.size() == 1) {

                            Range range = (Range) ranges.elementAt(0);
                            resp.addHeader("Content-Range", "bytes "
                                               + range.start
                                               + "-" + range.end + "/"
                                               + range.fileLength);
                            resp.setContentLength((int) range.length);
                            resp.setContentType
                                (revisionDescriptor.getContentType());

                            if (printContent) {
                                resp.setBufferSize(output);
                                copy(resourceInfo, is, os, range);
                            }

                        } else {

                            resp.setContentType
                                ("multipart/byteranges; boundary="
                                     + mimeSeparation);

                            if (printContent) {
                                resp.setBufferSize(output);
                                copy(resourceInfo, is, os,
                                     ranges.elements(),
                                     revisionDescriptor.getContentType());
                            }

                        }

                    }

                } else {
                    resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
                }

            } else {
                resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
            }

        } catch (Exception e) {
            int statusCode = getErrorCode( e );
            sendError( statusCode, e );
            throw new WebdavException( statusCode );
        }

    }




    /**
     * Get return status based on exception type.
     */
    protected int getErrorCode(Exception ex) {
        try {
            throw ex;
        } catch (RevisionNotFoundException e) {
            return WebdavStatus.SC_NOT_FOUND;
        } catch (RevisionContentNotFoundException e) {
            return WebdavStatus.SC_NOT_FOUND;
        } catch (RevisionDescriptorNotFoundException e) {
            return WebdavStatus.SC_NOT_FOUND;
        } catch (LinkedObjectNotFoundException e) {
            return WebdavStatus.SC_NOT_FOUND;
        } catch (Exception e) {
            return super.getErrorCode(e);
        }
    }



    // -------------------------------------------------------- Private Methods


    /**
     * Check if the conditions specified in the optional If headers are
     * satisfied.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param resourceInfo File object
     * @return boolean true if the resource meets all the specified conditions,
     * and false if any of the conditions is not satisfied, in which case
     * request processing is stopped
     */
    private boolean checkIfHeaders(HttpServletRequest request,
                                   HttpServletResponse response,
                                   ResourceInfo resourceInfo)
        throws IOException {

        String eTag = getETag(resourceInfo, true);
        long fileLength = resourceInfo.length;
        long lastModified = resourceInfo.date;

        StringTokenizer commaTokenizer;

        String headerValue;

        // Checking If-Match
        headerValue = request.getHeader("If-Match");
        if (headerValue != null) {
            if (headerValue.indexOf("*") == -1) {

                commaTokenizer = new StringTokenizer(headerValue, ",");
                boolean conditionSatisfied = false;

                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
                    String currentToken = commaTokenizer.nextToken();
                    if (currentToken.trim().equals(eTag))
                        conditionSatisfied = true;
                }

                // If none of the given ETags match, 412 Precodition failed is
                // sent back
                if (!conditionSatisfied) {
                    response.sendError
                        (HttpServletResponse.SC_PRECONDITION_FAILED);
                    return false;
                }

            }
        }

        // Checking If-Modified-Since
        headerValue = request.getHeader("If-Modified-Since");
        if (headerValue != null) {

            // If an If-None-Match header has been specified, if modified since
            // is ignored.
            if (request.getHeader("If-None-Match") == null) {

                Date date = null;

                // Parsing the HTTP Date
                for (int i = 0; (date == null) && (i < formats.length); i++) {
                    try {
                        synchronized (formats[i]) {
                            date = formats[i].parse(headerValue);
                        }
                    } catch (ParseException e) {
                        ;
                    }
                }

                if ((date != null)
                    && (lastModified <= (date.getTime() + 1000)) ) {
                    // The entity has not been modified since the date
                    // specified by the client. This is not an error case.
                    response.sendError
                        (HttpServletResponse.SC_NOT_MODIFIED);
                    return false;
                }

            }

        }

        // Checking If-None-Match
        headerValue = request.getHeader("If-None-Match");
        if (headerValue != null) {
            if (headerValue.indexOf("*") == -1) {

                commaTokenizer = new StringTokenizer(headerValue, ",");
                boolean conditionSatisfied = false;

                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
                    String currentToken = commaTokenizer.nextToken();
                    if (currentToken.trim().equals(eTag))
                        conditionSatisfied = true;
                }

                if (conditionSatisfied) {

                    // For GET and HEAD, we should respond with
                    // 304 Not Modified.
                    // For every other method, 412 Precondition Failed is sent
                    // back.
                    if ( ("GET".equals(request.getMethod()))
                        || ("HEAD".equals(request.getMethod())) ) {
                        response.sendError
                            (HttpServletResponse.SC_NOT_MODIFIED);
                        return false;
                    } else {
                        response.sendError
                            (HttpServletResponse.SC_PRECONDITION_FAILED);
                        return false;
                    }
                }

            } else {
                if (resourceInfo.exists()) {

                }
            }
        }

        // Checking If-Unmodified-Since
        headerValue = request.getHeader("If-Unmodified-Since");
        if (headerValue != null) {

            Date date = null;

            // Parsing the HTTP Date
            for (int i = 0; (date == null) && (i < formats.length); i++) {
                try {
                    synchronized (formats[i]) {
                        date = formats[i].parse(headerValue);
                    }
                } catch (ParseException e) {
                    ;
                }
            }

            if ( (date != null) && (lastModified > date.getTime()) ) {
                // The entity has not been modified since the date
                // specified by the client. This is not an error case.
                response.sendError
                    (HttpServletResponse.SC_PRECONDITION_FAILED);
                return false;
            }

        }

        return true;
    }


    /**
     * Get the ETag value associated with a file.
     *
     * @param resourceInfo File object
     * @param strong True if we want a strong ETag, in which case a checksum
     * of the file has to be calculated
     */
    private String getETagValue(ResourceInfo resourceInfo, boolean strong) {
        // FIXME : Compute a strong ETag if requested, using an MD5 digest
        // of the file contents
        return resourceInfo.length + "-" + resourceInfo.date;
    }


    /**
     * Get the ETag associated with a file.
     *
     * @param resourceInfo File object
     * @param strong True if we want a strong ETag, in which case a checksum
     * of the file has to be calculated
     */
    private String getETag(ResourceInfo resourceInfo, boolean strong) {
        if (strong)
            return "\"" + getETagValue(resourceInfo, strong) + "\"";
        else
            return "W/\"" + getETagValue(resourceInfo, strong) + "\"";
    }


    /**
     * Copy the contents of the specified input stream to the specified
     * output stream, and ensure that both streams are closed before returning
     * (even in the face of an exception).
     *
     * @param istream The input stream to read from
     * @param ostream The output stream to write to
     *
     * @exception IOException if an input/output error occurs
     */
    private void copy(ResourceInfo resourceInfo,
                      InputStream resourceInputStream,
                      ServletOutputStream ostream)
        throws IOException {

        IOException exception = null;

        InputStream istream = new BufferedInputStream
            (resourceInputStream, input);

        // Copy the input stream to the output stream
        exception = copyRange(istream, ostream);

        // Clean up the input and output streams
        try {
            istream.close();
        } catch (Throwable t) {
            ;
        }

        try {
            ostream.flush();
        } catch (Throwable t) {
            ;
        }
        try {
            ostream.close();
        } catch (Throwable t) {
            ;
        }

        // Rethrow any exception that has occurred
        if (exception != null)
            throw exception;

    }


    /**
     * Copy the contents of the specified input stream to the specified
     * output stream, and ensure that both streams are closed before returning
     * (even in the face of an exception).
     *
     * @param resourceInfo The ResourceInfo object
     * @param ostream The output stream to write to
     * @param range Range the client wanted to retrieve
     * @exception IOException if an input/output error occurs
     */
    private void copy(ResourceInfo resourceInfo,
                      InputStream resourceInputStream,
                      ServletOutputStream ostream,
                      Range range)
        throws IOException {

        IOException exception = null;

        InputStream istream =
            new BufferedInputStream(resourceInputStream, input);
        exception = copyRange(istream, ostream, range.start, range.end);

        // Clean up the input and output streams
        try {
            istream.close();
        } catch (Throwable t) {
            ;
        }
        try {
            ostream.flush();
        } catch (Throwable t) {
            ;
        }
        try {
            ostream.close();
        } catch (Throwable t) {
            ;
        }

        // Rethrow any exception that has occurred
        if (exception != null)
            throw exception;

    }


    /**
     * Copy the contents of the specified input stream to the specified
     * output stream, and ensure that both streams are closed before returning
     * (even in the face of an exception).
     *
     * @param resourceInfo The ResourceInfo object
     * @param ostream The output stream to write to
     * @param ranges Enumeration of the ranges the client wanted to retrieve
     * @param contentType Content type of the resource
     * @exception IOException if an input/output error occurs
     */
    private void copy(ResourceInfo resourceInfo,
                      InputStream resourceInputStream,
                      ServletOutputStream ostream,
                      Enumeration ranges, String contentType)
        throws IOException {

        IOException exception = null;

        while ( (exception == null) && (ranges.hasMoreElements()) ) {

            InputStream istream =
                new BufferedInputStream(resourceInputStream, input);

            Range currentRange = (Range) ranges.nextElement();

            // Writing MIME header.
            ostream.println("--" + mimeSeparation);
            if (contentType != null)
                ostream.println("Content-Type: " + contentType);
            ostream.println("Content-Range: bytes " + currentRange.start
                                + "-" + currentRange.end + "/"
                                + currentRange.fileLength);
            ostream.println();

            // Printing content
            exception = copyRange(istream, ostream, currentRange.start,
                                  currentRange.end);

            try {
                istream.close();
            } catch (Throwable t) {
                ;
            }

        }

        ostream.print("--" + mimeSeparation + "--");

        // Clean up the output streams
        try {
            ostream.flush();
        } catch (Throwable t) {
            ;
        }
        try {
            ostream.close();
        } catch (Throwable t) {
            ;
        }

        // Rethrow any exception that has occurred
        if (exception != null)
            throw exception;

    }


    /**
     * Copy the contents of the specified input stream to the specified
     * output stream, and ensure that both streams are closed before returning
     * (even in the face of an exception).
     *
     * @param istream The input stream to read from
     * @param ostream The output stream to write to
     * @return Exception which occured during processing
     */
    private IOException copyRange(InputStream istream,
                                  ServletOutputStream ostream) {

        // Copy the input stream to the output stream
        IOException exception = null;
        byte buffer[] = new byte[input];
        int len = buffer.length;
        while (true) {
            try {
                len = istream.read(buffer);
                if (len == -1)
                    break;
                ostream.write(buffer, 0, len);
            } catch (IOException e) {
                exception = e;
                len = -1;
                break;
            }
        }
        return exception;

    }


    /**
     * Copy the contents of the specified input stream to the specified
     * output stream, and ensure that both streams are closed before returning
     * (even in the face of an exception).
     *
     * @param istream The input stream to read from
     * @param ostream The output stream to write to
     * @param start Start of the range which will be copied
     * @param end End of the range which will be copied
     * @return Exception which occured during processing
     */
    private IOException copyRange(InputStream istream,
                                  ServletOutputStream ostream,
                                  long start, long end) {

        try {
            istream.skip(start);
        } catch (IOException e) {
            return e;
        }

        IOException exception = null;
        long bytesToRead = end - start + 1;

        byte buffer[] = new byte[input];
        int len = buffer.length;
        while ( (bytesToRead > 0) && (len >= buffer.length)) {
            try {
                len = istream.read(buffer);
                if (bytesToRead >= len) {
                    ostream.write(buffer, 0, len);
                    bytesToRead -= len;
                } else {
                    ostream.write(buffer, 0, (int) bytesToRead);
                    bytesToRead = 0;
                }
            } catch (IOException e) {
                exception = e;
                len = -1;
            }
            if (len < buffer.length)
                break;
        }

        return exception;

    }


    /**
     * Parse the range header.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @return Vector of ranges
     */
    private Vector parseRange(HttpServletRequest request,
                              HttpServletResponse response,
                              ResourceInfo resourceInfo)
        throws IOException {

        // Checking If-Range
        String headerValue = request.getHeader("If-Range");
        if (headerValue != null) {

            String eTag = getETag(resourceInfo, true);
            long lastModified = resourceInfo.date;

            Date date = null;

            // Parsing the HTTP Date
            for (int i = 0; (date == null) && (i < formats.length); i++) {
                try {
                    synchronized (formats[i]) {
                        date = formats[i].parse(headerValue);
                    }
                } catch (ParseException e) {
                    ;
                }
            }

            if (date == null) {

                // If the ETag the client gave does not match the entity
                // etag, then the entire entity is returned.
                if (!eTag.equals(headerValue.trim()))
                    return null;

            } else {

                // If the timestamp of the entity the client got is older than
                // the last modification date of the entity, the entire entity
                // is returned.
                if (lastModified > (date.getTime() + 1000))
                    return null;

            }

        }

        long fileLength = resourceInfo.length;

        if (fileLength == 0)
            return null;

        // Retrieving the range header (if any is specified
        String rangeHeader = request.getHeader("Range");

        if (rangeHeader == null)
            return null;
        // bytes is the only range unit supported (and I don't see the point
        // of adding new ones).
        if (!rangeHeader.startsWith("bytes")) {
            response.sendError
                (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return null;
        }

        rangeHeader = rangeHeader.substring(6);


        // Vector which will contain all the ranges which are successfully
        // parsed.
        Vector result = new Vector();
        StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");

        // Parsing the range list
        while (commaTokenizer.hasMoreTokens()) {
            String rangeDefinition = commaTokenizer.nextToken();

            Range currentRange = new Range();
            currentRange.fileLength = fileLength;

            int dashPos = rangeDefinition.indexOf('-');

            if (dashPos == -1) {
                response.sendError
                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                return null;
            }

            if (dashPos == 0) {

                try {
                    long offset = Long.parseLong(rangeDefinition);
                    currentRange.start = fileLength + offset;
                    currentRange.end = fileLength - 1;
                } catch (NumberFormatException e) {
                    response.sendError
                        (HttpServletResponse
                             .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    return null;
                }

            } else {

                try {
                    currentRange.start = Long.parseLong
                        (rangeDefinition.substring(0, dashPos));
                    if (dashPos < rangeDefinition.length() - 1)
                        currentRange.end = Long.parseLong
                            (rangeDefinition.substring
                                 (dashPos + 1, rangeDefinition.length()));
                    else
                        currentRange.end = fileLength - 1;
                } catch (NumberFormatException e) {
                    response.sendError
                        (HttpServletResponse
                             .SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    return null;
                }

            }

            currentRange.length = (currentRange.end - currentRange.start + 1);
            if (!currentRange.validate()) {
                response.sendError
                    (HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                return null;
            }

            result.addElement(currentRange);
        }

        return result;
    }


    // ------------------------------------------------------ Range Inner Class


    private class Range {

        public long start;
        public long end;
        public long length;
        public long fileLength;

        /**
         * Validate range.
         */
        public boolean validate() {
            return ( (start >= 0) && (end >= 0) && (length > 0)
                        && (start <= end) && (end < fileLength) && (fileLength >= length));
        }

    }


    // ----------------------------------------------  ResourceInfo Inner Class


    private class ResourceInfo {


        /**
         * Constructor.
         *
         * @param pathname Path name of the file
         */
        public ResourceInfo(String path, NodeRevisionDescriptor properties) {

            this.path = path;
            this.exists = true;
            this.creationDate = properties.getCreationDateAsDate().getTime();
            this.date = properties.getLastModifiedAsDate().getTime();
            this.httpDate = properties.getLastModified();
            this.length = properties.getContentLength();

        }


        public String path;
        public long creationDate;
        public String httpDate;
        public long date;
        public long length;
        //public boolean collection;
        public boolean exists;


        /**
         * Test if the associated resource exists.
         */
        public boolean exists() {
            return exists;
        }


        /**
         * String representation.
         */
        public String toString() {
            return path;
        }


    }


}
TOP

Related Classes of org.apache.slide.webdav.method.GetMethod

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.