001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.components.extensions;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.gwt.CmsPrefetchSerializationPolicy;
034import org.opencms.gwt.shared.property.CmsPropertiesBean;
035import org.opencms.gwt.shared.property.CmsPropertyChangeSet;
036import org.opencms.gwt.shared.rpc.I_CmsVfsService;
037import org.opencms.main.CmsException;
038import org.opencms.main.CmsLog;
039import org.opencms.ui.A_CmsUI;
040import org.opencms.ui.I_CmsUpdateListener;
041import org.opencms.ui.actions.CmsPropertiesDialogAction;
042import org.opencms.ui.shared.rpc.I_CmsPropertyClientRpc;
043import org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc;
044import org.opencms.ui.util.CmsNewResourceBuilder;
045import org.opencms.util.CmsUUID;
046
047import java.util.HashSet;
048import java.util.List;
049
050import org.apache.commons.logging.Log;
051
052import com.google.common.collect.Lists;
053import com.google.common.collect.Sets;
054import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
055import com.google.gwt.user.server.rpc.jakarta.RPC;
056import com.vaadin.server.AbstractExtension;
057import com.vaadin.ui.UI;
058
059/**
060 * Extension used for the GWT-based property dialog called from the workplace.
061 *
062 * This keeps track of the list of resources which were visible when the property dialog was opened, allowing
063 * the user to navigate through the list with prev/next buttons.
064 */
065public class CmsPropertyDialogExtension extends AbstractExtension implements I_CmsPropertyServerRpc {
066
067    /** Logger instance for this class. */
068    private static final Log LOG = CmsLog.getLog(CmsPropertyDialogExtension.class);
069
070    /** Serial version id. */
071    private static final long serialVersionUID = 1L;
072
073    /** The list of structure ids. */
074    List<CmsUUID> m_ids = Lists.newArrayList();
075
076    /** Current position in the ID list. */
077    int m_position;
078
079    /** Helper used to create a new resource after entering its properties. */
080    private CmsNewResourceBuilder m_newResourceBuilder;
081
082    /** The structure ids of possibly updated resources. */
083    private HashSet<CmsUUID> m_updatedIds = Sets.newHashSet();
084
085    /** The update listener. */
086    private I_CmsUpdateListener<String> m_updateListener;
087
088    /**
089     * Creates a new instance and binds it to a UI instance.<p>
090     *
091     * @param ui the UI to bind this extension to
092     * @param updateListener the update listener
093     */
094    public CmsPropertyDialogExtension(UI ui, I_CmsUpdateListener<String> updateListener) {
095
096        extend(ui);
097        m_updateListener = updateListener;
098        registerRpc(this, I_CmsPropertyServerRpc.class);
099    }
100
101    /**
102     * Open property editor for the resource with the given structure id.<p>
103     *
104     * @param structureId the structure id of a resource
105     * @param allIds structure ids of resources for the prev/next navigation
106     * @param editName controls whether the file name should be editable
107     */
108    public void editProperties(CmsUUID structureId, List<CmsUUID> allIds, boolean editName) {
109
110        m_position = allIds.indexOf(structureId);
111
112        m_ids = allIds;
113        m_updatedIds.add(structureId);
114        boolean online = A_CmsUI.getCmsObject().getRequestContext().getCurrentProject().isOnlineProject();
115        getRpcProxy(I_CmsPropertyClientRpc.class).editProperties(
116            "" + structureId,
117            editName,
118            online || (allIds.size() < 2));
119    }
120
121    /**
122     * Opens the property dialog for a resource to be created with the 'New' dialog.<p>
123     *
124     * @param builder the resource builder used by the 'New' dialog to create the resource
125     */
126    public void editPropertiesForNewResource(CmsNewResourceBuilder builder) {
127
128        try {
129            CmsPropertiesBean propData = builder.getPropertyData();
130            String serializedPropData = RPC.encodeResponseForSuccess(
131                I_CmsVfsService.class.getMethod("loadPropertyData", CmsUUID.class),
132                propData,
133                CmsPrefetchSerializationPolicy.instance());
134            getRpcProxy(I_CmsPropertyClientRpc.class).editPropertiesForNewResource(serializedPropData);
135            m_newResourceBuilder = builder;
136        } catch (Exception e) {
137            throw new RuntimeException(e);
138        }
139    }
140
141    /**
142     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#onClose(long)
143     */
144    public void onClose(long delayMillis) {
145
146        remove();
147        if (delayMillis > 0) {
148            try {
149                Thread.sleep(delayMillis);
150            } catch (InterruptedException e) {
151                // ignore
152            }
153        }
154        List<String> updates = Lists.newArrayList();
155        for (CmsUUID id : m_updatedIds) {
156            updates.add("" + id);
157        }
158        m_updateListener.onUpdate(updates);
159    }
160
161    /**
162     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#removeExtension()
163     */
164    public void removeExtension() {
165
166        remove();
167    }
168
169    /**
170     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#requestNextFile(int)
171     */
172    public void requestNextFile(int offset) {
173
174        int newPos = m_position;
175        int count = 0;
176        do {
177            newPos = nextIndex(newPos, offset);
178            count += 1;
179            if (count > m_ids.size()) {
180                // prevent infinite loop in case user suddenly can't edit any properties anymore (e.g. through a permission change)
181                newPos = m_position;
182                break;
183            }
184        } while (!canEdit(m_ids.get(newPos)));
185        m_position = newPos;
186        CmsUUID nextId = m_ids.get(m_position);
187        m_updatedIds.add(nextId);
188        getRpcProxy(I_CmsPropertyClientRpc.class).sendNextId("" + nextId);
189    }
190
191    /**
192     * @see org.opencms.ui.shared.rpc.I_CmsPropertyServerRpc#savePropertiesForNewResource(java.lang.String)
193     */
194    public void savePropertiesForNewResource(String data) {
195
196        try {
197            getRpcProxy(I_CmsPropertyClientRpc.class).confirmSaveForNew();
198            ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
199                Thread.currentThread().getContextClassLoader(),
200                null);
201            // Filling stream reader with data
202            streamReader.prepareToRead(data);
203            // Reading deserialized object from the stream
204            CmsPropertyChangeSet changes = (CmsPropertyChangeSet)(streamReader.readObject());
205            m_newResourceBuilder.setPropertyChanges(changes);
206            m_newResourceBuilder.safeCreateResource();
207            remove();
208        } catch (Exception e) {
209            throw new RuntimeException(e);
210        }
211    }
212
213    /**
214     * Checks if the user can edit the resource with the given id.<p>
215     *
216     * @param id a structure id
217     * @return true if the user can edit the file
218     */
219    protected boolean canEdit(CmsUUID id) {
220
221        CmsObject cms = A_CmsUI.getCmsObject();
222        CmsResource res = null;
223        try {
224            res = cms.readResource(id, CmsResourceFilter.ALL);
225            boolean result = CmsPropertiesDialogAction.VISIBILITY.getVisibility(
226                A_CmsUI.getCmsObject(),
227                Lists.newArrayList(res)).isActive();
228            return result;
229        } catch (CmsException e) {
230            LOG.error(e.getLocalizedMessage(), e);
231            return false;
232        }
233    }
234
235    /**
236     * Computes the next index.<p>
237     *
238     * This method by called more than once per press of next/prev, since uneditable resources need to be skipped.
239     *
240     * @param pos the current position
241     * @param offset the offset (+1 or -1)
242     * @return the next index
243     */
244    int nextIndex(int pos, int offset) {
245
246        return (pos + offset + m_ids.size()) % m_ids.size();
247    }
248
249}